Search code examples
angularjsangularjs-q

$q.all in angular-ui modal controller resolving early after consecutive open/closes


I currently have a modal (Angular-UI Modal) that, when opened for the first time, works perfectly. However, after I close it and then re-open it, things start acting a little strange.

The code:

(function () {
    'use strict';

    angular.module('app.modals').controller('personalizeNewsSubscriptionsModalCtrl', ['$scope', '$log', '$modalInstance', '$q', 'termStoreSvc', 'userNewsSubscriptionsSvc',
            function ($scope, $log, $modalInstance, $q, termStore, userSub) {
                //#region Variable declaration
                var allTopics = [],
                    userSubs = [],
                    subscriptions = new termStore('Manaaged Metadata Service', 'TS Today', 'Topic'),
                    userSubscriptions = new userSub('blackba');

                var allSubsLoaded = subscriptions.ready(function () {
                        allTopics = subscriptions.terms;
                    }),
                    userSubsLoaded = userSubscriptions.ready(function () {
                        userSubs = userSubscriptions.subscriptions;
                    });

                $log = $log.getInstance('app.modals.personalizeNewsSubscriptionsModalCtrl');
                //#endregion                

                $q.all([allSubsLoaded, userSubsLoaded]).then(function () {
                    // use allTopics and userSubs to create $scope.subscriptions
                    $scope.subscriptions = allTopics.map(function (obj) {
                        return {
                            Title: obj,
                            Selected: userSubs.indexOf(obj) !== -1
                        }
                    });
                    $log.debug('Subscriptions object loaded', $scope.subscriptions);
                });

                subscriptions.init();
                userSubscriptions.init();                

                $scope.close = function () {
                    $modalInstance.dismiss('cancel');
                }
            }]);
})();

The allSubsLoaded and userSubsLoaded objects are promises. To understand how those are resolved, here's a simplified version of my services

(function () {
    'use strict';

    angular.module('app.services.sharepoint')
        .factory('termStoreSvc', ['$log', '$q', function ($log, $q) {
            var self = undefined,
                deferred = $q.defer(),
                promise = deferred.promise;

            var termStoreSvc = function () {                
                self = this;

                self.init = init;
                self.ready = function (fn) {
                    return promise.then(fn);
                }
            };            

            function init() {
                $log.debug('Initializing');

                // do some stuff here                

                $log.debug('Initialized');
                deferred.resolve();
            };


            return termStoreSvc;
        }]);
})();

When I call init, it does some stuff and then resolves a hidden promise I declared so then when ready() is used with a callback function, the callback won't execute until the init() method is done.

For some reason, after I open/close it the first time and open it up a second/third/etc... time, the $q.all({array of promises}) is resolving early. Any ideas why?

Code Example for the solution:

The issue was that I didn't have the promise and deferred objects on the object I returned from the factory, which means that they were persisted (so after the first time they were still showing as resolved, causing my $q.all() to finish "early").

(function () {
    'use strict';

    angular.module('app.services.sharepoint')
        .factory('termStoreSvc', ['$log', '$q', function ($log, $q) {
            var self = undefined;

            var termStoreSvc = function () {                
                self = this;

                self.init = init;

                self.deferred = $q.defer();
                self.promise = self.deferred.promise;
                self.ready = function (fn) {
                    return self.promise.then(fn);
                }
            };            

            function init() {
                $log.debug('Initializing');

                // do some stuff here                

                $log.debug('Initialized');
                self.deferred.resolve();
            };


            return termStoreSvc;
        }]);
})();

Solution

  • An Angular factory is a singleton. Anything declared in the factory definition itself will be instantiated once for the life of the program. Anything declared in the object that the factory returns will, of course, be instantiated every time you new an instance from it. In your case a promise is declared in the factory definition and each instance the factory creates references that same promise. Hence your promise is already resolved for all instances apart from the first.