AngularJS Scopes and Deferreds

2019-07-19 01:32发布

问题:

I have a service that does something hard and returns a promise:

.factory('myService', function($q) {

    return {
        doSomethingHard: function() {
            var deferred = $q.defer();

            setTimeout(function() {
                deferred.resolve("I'm done!");
            }, 1000);

            return deferred.promise;
        }
    };
})

I have a controller that adds a function to the scope using that service:

.controller('MyCtrl', function($scope, myService) {

    $scope.doSomething = function() {
        var promise = myService.doSomethingHard();

        promise.then(function(result) {
            alert(result);
        });
    };

})

I use a directive to call that controller function by parsing an attribute:

.directive('myDirective', function($parse) {
    return {
        link: function(scope, el, attr) {

            var myParsedFunction = $parse(attr.myDirective);

            el.bind('click', function() {
                myParsedFunction(scope);
            });
        }
    };
})

with the template

<div ng-controller="MyCtrl">
    <button my-directive="doSomething()">The Button</button>
</div>

Clicking the button triggers the event listener, which calls the controller function doSomething, which calls the service function doSomethingHard, which returns a promise, THAT IS NEVER RESOLVED.

Whole thing up on a fiddle here:

http://jsfiddle.net/nicholasstephan/RgKaT/

What gives?

Thanks.

EDIT: Thanks to Maksym H., It looks like wrapping the promise resolve in $scope.$apply() makes it fire in the controller. I've got a working fiddle up http://jsfiddle.net/RgKaT/4/. But I'd really like to keep the scope out of my services.

I'd also really like to know why this works. Or better yet, why it doesn't work without resolving the promise while wrapped in a scope apply. The whole Angular world vs regular Javascript world analogy makes sense when thinking about properties as changes need to be digested, but this is a promise... with callback functions. Does $q just flag the promise as resolved and wait for the scope to digest that property update and fire its resolved handler functions?

回答1:

Here is another way: Try to define scope in directive and bind this attr to expect parent scope.

.directive('myDirective', function() {
  return {
    scope: { myDirective: "=" }, // or { myParsedFunction: "=myDirective" },
    link: function(scope, el, attr) {

        el.bind('click', function() {
            scope.myDirecive(scope); // or scope.myParsedFunction(scope)
        });
    }
  };
})

But the main thing is to run digest when you resolving it after some time:

.factory('myService', function($q, $timeout) {

    return {
        doSomethingHard: function() {
            alert('3. doing something hard');

            var deferred = $q.defer();

            // using $timeout as it's working better with promises
            $timeout(function() {
                alert('4. resolving deferred');
                deferred.resolve('Hello World!'); // Here...
            }, 1000);

            return deferred.promise;
        }
    };
})

jsFiddle

P.S. Make sure you are passing method as model of parent scope not applying this by "()" in HTML

<button my-directive="doSomething">Button</button>



回答2:

Just replace setTimeout with $timeout (and remember to inject $timeout into your service). Here's an updated jsFiddle.