Вопрос

My directive calls some async service based on $http. Service name comes to derectiva as a parameter:

.directive('lookup', ['$scope',  function($scope, svc) {

  var injector = angular.injector(['app', 'ng']);
  var svc = injector.get(scope.serviceName);
  ...
  $scope.clickHandler = function () {

    svc.lookup($scope.inputText).then(function (res) {

      $scope.inputText = res.data.name;
    });
  }
  ...
})

While setting chrome breakpoint inside then() handler we can see by call stack that it's fired within $apply(), so every scope change should be reflected in DOM. $q documentation also states that handlers should be synchronized with scope/dom update cycle, but my view is not updated after clickHandler(). Calling explicit scope.$digest() after scope change does the thing, but don't know why it does not work withouth it. Another strange thing I've noticed is that on the breakpoint $scope has no parent-child relation with $rootScope for which http response handler is wrapped by $apply(). Maybe this is the reason of my troubles. Am I doing something wrong?

Real code is hard to cite, but see essential example code on plunker

Это было полезно?

Решение

Explanation:

The villain of the piece is the use of angular.injector in the link function:

var injector = angular.injector(['ng', 'app']);
var lookupService = injector.get(scope.serviceName);

This code will create a new injector function instead of retrieving the one already created for your application when it was bootstrapped.

Services in Angular are singletons in the sense that they are only created once per injector. In your case this means that injector.get(scope.serviceName) will return a new instance of the service and not the same instance as you possibly would've interacted with before.

The same goes for the $http service that your lookupService uses. In turn, $http injects $rootScope to trigger the digest cycle by calling $apply on it after a XHR request. But as the $rootScope will not be the same as for the rest of your application, the digest cycle will be in vain.

This is also the reason for the behaviors you are witnessing, and why an explicit call to scope.$digest() helps, as it will trigger the digest cycle on the correct scope chain.

Solution:

Inject the $injector service in your directive and use that to retrieve the service you want:

.directive('lookup', ['$injector',
  function($injector) {
    function link(scope, elem) {

      var lookupService = $injector.get(scope.serviceName);

Demo: http://plnkr.co/edit/nubMohsbHAEkbYSg39xV?p=preview

Лицензировано под: CC-BY-SA с атрибуция
Не связан с StackOverflow
scroll top