2014-11-14 13 views
17

ja wpadłem na problem z NG-kontrolera i „resolve” funkcjonalność:angularjs NG-kontroler z determinacji

Mam kontroler, który wymaga pewnej zależności, które należy rozwiązać przed działa, to działa dobrze, kiedy zdefiniować poprzez nG-trasie:

kod Kontroler wygląda następująco:

angular.module('myApp') 
    .controller('MyController', ['$scope', 'data', function ($scope, data) { 
     $scope.data = data; 
    } 
    ] 
); 

Routing:

... 
.when('/someUrl', { 
     templateUrl : 'some.html', 
     controller : 'MyController', 
     resolve : { 
      data: ['Service', function (Service) { 
      return Service.getData(); 
      }] 
     } 
}) 
... 

kiedy idę do/someUrl, wszystko działa.

Ale muszę używać tego kontrolera w inny sposób (muszę w obie strony w różnych miejscach):

<div ng-controller="MyController">*some html here*</div> 

I, oczywiście, że nie, ponieważ zależność „dane” nie został rozwiązany. Czy istnieje sposób na wstrzyknięcie zależności do kontrolera, kiedy używam "ng-kontrolera", czy powinienem zrezygnować i załadować dane do kontrolera?

+0

Mogę napisać Ci przykład później. Ale w twoim przypadku, aby usługa zwróciła obietnicę, a nie rzeczywiste dane. Po rozwiązaniu, pobierz dane w MyController, oznacza to, że Twoja Usługa musi mieć metodę lub właściwość, która zwraca dane z pamięci podręcznej. Mam nadzieję że to pomogło. – Lance

+0

Wpadłem na ten sam problem http://jsfiddle.net/cnst530p/6/ –

Odpowiedz

3

Utwórz nowy moduł, w którym masz usługę wstrzykiwania, jak pokazano poniżej.

var module = angular.module('myservice', []); 

module.service('userService', function(Service){ 
    return Service.getData(); 
}); 

Inject nowo utworzony moduł serwisowy wewnątrz modułu aplikacji

angular.module('myApp') 
    .controller('MyController', ['$scope', 'myservice', function ($scope, myservice) { 
     $scope.data = data; 
    // now you can use new dependent service anywhere here. 
    } 
    ] 
); 
+0

Wow, to wygląda jak włamanie, ale piękne, i działa. Wielkie dzięki. – Rasalom

+1

Albo nie. "myservice" ładuje dane tylko po uruchomieniu kodu kontrolera. Ale ideą "rozwiązania" jest ładowanie danych przed uruchomieniem kodu kontrolera. W każdym razie dzięki za próby. * Przepraszam, zajrzałem do niewłaściwej części kodu po edycji * – Rasalom

+0

@Rasalom rozwiązałeś? Mam ten sam numer – fabricioflores

0

pobierania danych w „rozwiązać” cechą jest funkcjonalność trasy (routeProvider), a nie funkcjonalność sterownika.

Klucz (jest twój przypadek: "dane") w atrybucie rozstrzygnięcia jest wstrzykiwany jako usługa. Dlatego jesteśmy w stanie pobrać dane z tej usługi.

Ale aby użyć tego samego kontrolera w innym miejscu, musisz pobrać dane z kontrolera.

2

Zakładając, że Service.getData() zwróci obietnicę, MyController może również wstrzyknąć tę Usługę. Problem polega na tym, że chcesz opóźnić działanie kontrolera, dopóki obietnica nie zostanie rozwiązana. Router robi to za Ciebie, używanie kontrolera bezpośrednio oznacza, że ​​musisz zbudować tę logikę.

angular.module('myApp') 
    .controller('MyController', ['$scope', 'Service', function ($scope, Service) { 
    $scope.data = {}; // default values for data 
    Service.getData().then(function(data){ 
     // data is now resolved... do stuff with it 
     $scope.data = data; 
    }); 
    }] 
); 

Teraz ta działa świetnie przy użyciu kontrolera bezpośrednio, ale w routingu przykład, w którym chcesz, aby opóźnić renderowania strony aż dane zostaną rozwiązane, idziesz do końca się co dwa połączenia do Service.getData (). Istnieje kilka sposobów, aby obejść ten problem, jak mający Service.getData() zwracają taką samą obietnicę dla wszystkich rozmówcy, lub coś takiego może działać, aby uniknąć drugiego połączenia w całości:

angular.module('myApp') 
    .controller('MyController', ['$scope', '$q', 'Service', function ($scope, $q, Service) { 
    var dataPromise, 
     // data might be provided from router as an optional, forth param 
     maybeData = arguments[3]; // have not tried this before 
    $scope.data = {}; //default values 
    // if maybeData is available, convert it to a promise, if not, 
    // get a promise for fetching the data 
    dataPromise = !!maybeData?$q.when(maybeData):Service.getData(); 
    dataPromise.then(function(data){ 
     // data is now resolved... do stuff with it 
     $scope.data = data; 
    });  
    }] 
); 
+0

Nie sądzę, że rozumiesz, co jest tutaj potrzebne. Chciałem użyć funkcji 'resolve' z trasy bez routingu, co nie jest możliwe. Ale tutaj opisałeś teraz działanie '$ q'. – Rasalom

11

W poniżej, w celu rozwiązania trasy rozwiązujemy obietnicę i zawijamy dane zwracane w obiekcie z właściwością. Następnie duplikujemy tę strukturę w usłudze opakowania ("usługa danych"), której używamy do formularza kontrolera ng.

Usługa pakowania również rozwiązuje obietnicę, ale robi to wewnętrznie i aktualizuje właściwość obiektu, który już został zwrócony, aby został zużyty przez kontroler.

W kontrolerze prawdopodobnie można umieścić obserwatora na tej właściwości, jeśli chcesz opóźnić niektóre dodatkowe zachowania, dopóki wszystko nie zostanie rozwiązane, a dane będą dostępne.

Alternatywnie, pokazałem, używając kontrolera, który "owija" inny kontroler; po rozwiązaniu obietnicy z usługi, przekazuje on swój własny zakres $ do kontrolowanego kontrolera, jak również danych z usługi.

Pamiętaj, że skorzystałem z opcji $ timeout, aby zapewnić zwłokę w zwrocie 1000ms, aby spróbować wyjaśnić, co się dzieje i kiedy.

angular.module('myApp', ['ngRoute']) 
 
    .config(function($routeProvider) { 
 
    $routeProvider 
 
     .when('/', { 
 
     template: '<h1>{{title}}</h1><p>{{blurb}}</p><div ng-controller="ResolveController">Using ng-controller: <strong>{{data.data}}</strong></div>', 
 
     controller: 'HomeController' 
 
     }) 
 
     .when('/byResolve', { 
 
     template: '<h1>{{title}}</h1><p>{{blurb}}</p><p>Resolved: <strong>{{data.data}}</strong></p>', 
 
     controller: "ResolveController", 
 
     resolve: { 
 
      dataService: ['Service', 
 
      function(Service) { 
 
       // Here getData() returns a promise, so we can use .then. 
 
       // I'm wrapping the result in an object with property 'data', so we're returning an object 
 
       // which can be referenced, rather than a string which would only be by value. 
 
       // This mirrors what we return from dataService (which wraps Service), making it interchangeable. 
 
       return Service.getData().then(function(result) { 
 
       return { 
 
        data: result 
 
       }; 
 
       }); 
 
      } 
 
      ] 
 
     } 
 
     }) 
 
     .when('/byWrapperController', { 
 
     template: '<h1>Wrapped: {{title}}</h1><p>{{blurb}}</p><div ng-controller="WrapperController">Resolving and passing to a wrapper controller: <strong>{{data.data ? data.data : "Loading..."}}</strong></div>', 
 
     controller: 'WrapperController' 
 
     }); 
 
    }) 
 
    .controller('HomeController', function($scope) { 
 
    $scope.title = "ng-controller"; 
 
    $scope.blurb = "Click 'By Resolve' above to trigger the next route and resolve."; 
 
    }) 
 
    .controller('ResolveController', ['$scope', 'dataService', 
 
    function($scope, dataService) { 
 
     $scope.title = "Router and resolve"; 
 
     $scope.blurb = "Click 'By ng-controller' above to trigger the original route and test ng-controller and the wrapper service, 'dataService'."; 
 
     $scope.data = dataService; 
 
    } 
 
    ]) 
 
    .controller('WrapperController', ['$scope', '$controller', 'Service', 
 
    function($scope, $controller, Service) { 
 
     $scope.title = "Resolving..."; //this controller could of course not show anything until after the resolve, but demo purposes... 
 
     Service.getData().then(function(result) { 
 
     $controller('ResolveController', { 
 
      $scope: $scope, //passing the same scope on through 
 
      dataService: { 
 
      data: result 
 
      } 
 
     }); 
 
     }); 
 
    } 
 
    ]) 
 
    .service('Service', ['$timeout', 
 
    function($timeout) { 
 
     return { 
 
     getData: function() { 
 
      //return a test promise 
 
      return $timeout(function() { 
 
      return "Data from Service!"; 
 
      }, 1000); 
 
     } 
 
     }; 
 
    } 
 
    ]) 
 
    // our wrapper service, that will resolve the promise internally and update a property on an object we can return (by reference) 
 
    .service('dataService', function(Service) { 
 
    // creating a return object with a data property, matching the structure we return from the router resolve 
 
    var _result = { 
 
     data: null 
 
    }; 
 
    Service.getData().then(function(result) { 
 
     _result.data = result; 
 
     return result; 
 
    }); 
 
    return _result; 
 
    });
<script src="https://ajax.googleapis.com/ajax/libs/angularjs/1.2.27/angular.min.js"></script> 
 
<script src="https://ajax.googleapis.com/ajax/libs/angularjs/1.2.27/angular-route.min.js"></script> 
 
<div ng-app="myApp"> 
 
    <a href="#/">By ng-controller</a> | 
 
    <a href="#/byResolve">By Resolve</a> | 
 
    <a href="#/byWrapperController">By Wrapper Controller</a> 
 
    <div ng-view /> 
 
</div>

+0

Co ciekawe, w wersji "Według ng-kontrolera", po pierwszym rozwiązaniu, usługa opakowania nie musi już być ponownie rozpoznawana, nawet po zmianie trasy (jest to singleton, który został zainicjowany tylko raz). Pozostałe dwa wyzwalają rozwiązanie na nowo. "Przez Wrapper Controller" prawdopodobnie używałby usługi wrapper zamiast bezpośrednio z Service, aby uniknąć ciągłego ponownego rozstrzygania ... – JcT

+0

Dodatkowo, prawdopodobnie mógłbyś zrobić dyrektywę używając podobnej koncepcji do zapakowanego kontrolera, który może być bardziej wydajny i elastyczne. – JcT

1

starałem się rozwiązać ten problem za pomocą ng-init ale natknąłem następujących ostrzeżeń na angularjs.org

Jedyne właściwe wykorzystanie ngInit jest dla aliasing specjalne właściwości z ngRepeat, jak widać w poniższym demo. Poza tym przypadkiem powinieneś użyć używania kontrolerów zamiast ngInit, aby zainicjować wartości w zakresie.

Więc zacząłem szukać czegoś podobnego ng-resolve i natknąłem wątku:

https://github.com/angular/angular.js/issues/2092

Powyższy związek składa się ryba demo, które mają ng-resolve jak funkcjonalność. Myślę, że ng-resolve może stać się funkcją w przyszłych wersjach kątowych 1.x. Na razie możemy poradzić sobie z dyrektywą wymienioną w powyższym linku.

1

"Dane" z rozstrzygnięcia trasy nie będą dostępne do wstrzyknięcia dla kontrolera aktywowanego poza dostawcą trasy. będzie dostępny tylko dla widoku skonfigurowanego w dostawcy trasy.

jeśli chcesz, aby dane do kontrolera aktywowane bezpośrednio, inne niż aktywacja routeprovider, musisz wprowadzić do niego hack.

zobaczyć, czy ten link pozwala na to:

http://www.johnpapa.net/route-resolve-and-controller-activate-in-angularjs/

0

Spróbuj

Usługa:

(function() { 

var myService = function($http) { 
    var getData = function() { 
     //return your result 
    }; 
    return { 
     getData:getData 
    }; 
}; 
var myApp = angular.module("myApp"); 
myApp.factory("myService", myService); 
}()); 

Kontroler:

(function() { 
var myApp = angular.module("myApp"); 
myApp.controller('MyController', [ 
    '$scope', 'myService', function($scope, myService) { 
     $scope.data = myService.getData(); 
    } 
]); 

//Routing 
.when('/someUrl', { 
    templateUrl : 'some.html', 
    controller : 'MyController', 
    resolve : { 
     data: $scope.data, 
    } 
}) 
}()); 
3

Y możesz użyć mechanizmu prototypu.

.when('/someUrl', { 
    template : '<div ng-controller="MyController" ng-template="some.html"></div>', 
    controller: function (data) { 
     var pr = this; 
     pr.data = data; 
    }, 
    controllerAs: 'pr', 
    resolve : { 
     data: ['Service', function (Service) { 
      return Service.getData(); 
     }] 
    } 
}) 

angular.module('myApp') 
    .controller('MyController', ['$scope', function ($scope) { 
     $scope.data = $scope.pr.data; //magic 
    } 
    ] 
); 

Teraz gdziekolwiek chcesz użyć

'<div ng-controller="MyController"></div>' 

musisz upewnić się, że nie pr.data w Zakresie kontrolera wywołującego.Jako przykład UIB-modal

var modalInstance = $modal.open({ 
    animation: true, 
    templateUrl: 'modal.html', 
    resolve: { 
     data: ['Service', function (Service) { 
      return Service.getData(); 
     }] 
    }, 
    controller: function ($scope, $modalInstance, data) { 
     var pr = this; 
     pr.data = data; 
     pr.ok = function() { 
      $modalInstance.close(); 
     }; 
    }, 
    controllerAs:'pr', 
    size:'sm' 
}); 

modal.html

<script type="text/ng-template" id="modal.html"> 
    <div class="modal-body"> 
     <div ng-include="some.html" ng-controller="MyController"></div> 
    </div> 
    <div class="modal-footer"> 
     <button class="btn btn-primary pull-right" type="button" ng-click="pr.ok()">{{ 'ok' | capitalize:'first'}}</button> 
    </div> 
</script> 

I teraz można użyć $ scope.data = $ scope.pr.data; w MyController

pr.data to mój styl. Możesz przepisać kod bez PR. Podstawowa zasada pracy z ng-kontrolerem opisanym w tym wideo https://egghead.io/lessons/angularjs-the-dot