1

I was just playing around with angular and its DI. I tried to use the promises paradigm as well, however I encountered a rather strange problem, using it in a Chrome Extension.

The following code works beautifully. The promise is resolved, when cb() in the object literal in the "chromeStorageService" is called. But when I uncomment the return chrome.storage.local; the code stops working. I can't understand why, because the console.log('going to be resolved'); still gets fired, but the success alert does not. Thanks in advance for any tip :)

http://jsfiddle.net/Ku5dz/

https://developer.chrome.com/extensions/storage.html

'use strict';
var app = {};

app = angular.module('options', ['options.controllers']);

app.services = angular.module('options.services', []);


app.services.factory('chromeStorageService', function () {
    //return chrome.storage.local;


    return {
        get: function(id, cb) {
            cb();
        }
    }
});

app.services.factory('storageService', ['chromeStorageService', '$q', function (chromeStorage, $q) {
    return new function () {
        this.get = function (identifier) {
            var defered = $q.defer();

            chromeStorage.get(identifier, function (items) {
                var error = false//chrome.runtime.lastError;
                if (error) {
                    return defered.reject(error);
                }

                console.log('going to be resolved');
                console.log(defered);
                defered.resolve();
            });

            return defered.promise;
        }
    }
}]);

app.services.factory('fooService', ['storageService', function (testService) {
    return new function () {
        this.getAll = function () {
            var promise = testService.get('foo');
            console.log(promise);
            promise.then(
                function () {
                    alert('success');
                }, function () {
                    aler('err');
                }
            );
        }
    }
}]);


app.directive('foo', ['fooService', function (fooService) {
    return {
        restrict:'A',
        link:function (scope, elm, attrs, ctrl) {
            fooService.getAll();    
        }
    };
}]);


app.controllers = angular.module('options.controllers', ['options.services']);
Robin Drexler
  • 4,307
  • 3
  • 25
  • 28

4 Answers4

6

Angular has its own little event loop thing. When you .resolve a promise, that goes into Angular's queue. Stuff only happens when Angular clears its queue.

The usual thing is to do something like:

$scope.$apply(function() {
  promise.resolve(thing);
});

And when control returns to $apply from the function you passed to it, it clears the queue and everything is beautiful. In this case you probably need to inject $rootScope into the service so you have something to apply, as per The view is not updated in AngularJS.

Your stub works because the stub never leaves angular-world, so you're already in a context that will look after the queue. When you use chrome.storage.local, your callback is not called from such a context.

Community
  • 1
  • 1
Iain
  • 4,203
  • 23
  • 21
  • I had the same problem: calling a function in $scope returning a promise from a directive did not fire then() in the directive. To my surprise - your trick actually worked. I wrapped my resolve() and reject()s in $apply, things are working well. Do you have any reference to documentation explaining why? I find this pretty ugly and want to make sure that I am not doing something unnatural when using $q with Angular. Maybe I should not be calling deferred function from a directive to the $scope? – Frederic Fortier May 15 '13 at 05:57
  • http://jimhoskins.com/2012/12/17/angularjs-and-apply.html is a pretty good write-up. https://github.com/angular/angular.js/wiki/When-to-use-$scope.$apply%28%29 and http://docs.angularjs.org/guide/concepts#runtime discuss it a bit as well. – Iain May 19 '13 at 15:06
  • 1
    Thanks, this article is very good. I think that my issue is not so much the deferred implementation itself but the fact I am resolving it in a non-Angular async callback (I am being lazy and using $.ajax for comfort). I will try switching to the Angular equivalent which I expect would wrap my callback into a $scope.$apply() for me and simplify my syntax. – Frederic Fortier May 20 '13 at 01:51
1

I met the same problem today. I want to integrate chrome.storage.local.get with Angular promise pattern cause chrome.storage.local.get is asynchronous function. That's how i was doing:

// service
angular.module('app').factory('STORAGE', function($q) {
    return {
        get: function(key) {
            var deferred = $q.defer();

            chrome.storage.local.get(key, function(data) {
                deferred.resolve(data[key]);
            });

            return deferred.promise;
        },
        set: function(key, value) {
            var deferred = $q.defer();

            var data = {};
            data[key] = value;
            chrome.storage.local.set(data, function() {
                deferred.resolve({});
            });

            return deferred.promise;
        },
        del: function(key) {
            var deferred = $q.defer();

            chrome.storage.local.remove(key, function() {
                deferred.resolve({});
            });

            return deferred.promise;
        }
    }
});

I define a Angular service call STORAGE that i can encapsulate chrome.storage.local. get, set and del method.

Then we can inject STORAGE to Angular route resolve part

// route resolve
...
resolve: {
    hasToken: function(STORAGE) {
        return (STORAGE.get('instagram_access_token')) ? true : false;
    }
}
...

Inject route resolve parameter and STORAGE to controller

// controller 
angular.module('app').controller('HomeController', ['$scope', 'hasToken', 'STORAGE',
    function($scope, hasToken, STORAGE) {
        $scope.hasToken = hasToken;

        if ($scope.hasToken) {
            STORAGE.get('profile').then(function(data) {
                $scope.user = data;
            });
        }
    }
]);

It does work well.

cage.chung
  • 11
  • 2
0

Basically why your code is not running is because you are returning chrome.local.storage which takes you out of the scope and get function never gets a chance to get executed. see the chrome API of how to do that it will be very similar to angular's $http service

Arshabh Agarwal
  • 556
  • 2
  • 15
-2

if you uncomment return before the get , how do you except get call to be made??

basically you are coming out of function whenever you give return 'something'

in angular js you can call the then function using the following syntax

    $http.get(url).then(function(response){
           //put your callback code here
       }) 
Arshabh Agarwal
  • 556
  • 2
  • 15
  • chrome.storage.local also provides a method called "get" which calls the given callback. My object literal just mocks that behavior.https://developer.chrome.com/extensions/storage.html Or do I miss something? – Robin Drexler Jan 29 '13 at 21:43