From c0b4e2db9cbc8bc3164cedc4646145d3ab72536e Mon Sep 17 00:00:00 2001 From: Caitlin Potter Date: Thu, 17 Apr 2014 10:11:28 -0400 Subject: [PATCH] fix(injector): invoke config blocks for module after all providers This change ensures that a module's config blocks are always invoked after all of its providers are registered. BREAKING CHANGE: Previously, config blocks would be able to control behaviour of provider registration, due to being invoked prior to provider registration. Now, provider registration always occurs prior to configuration for a given module, and therefore config blocks are not able to have any control over a providers registration. Example: Previously, the following: angular.module('foo', []) .provider('$rootProvider', function() { this.$get = function() { ... } }) .config(function($rootProvider) { $rootProvider.dependentMode = "B"; }) .provider('$dependentProvider', function($rootProvider) { if ($rootProvider.dependentMode === "A") { this.$get = function() { // Special mode! } } else { this.$get = function() { // something else } } }); would have "worked", meaning behaviour of the config block between the registration of "$rootProvider" and "$dependentProvider" would have actually accomplished something and changed the behaviour of the app. This is no longer possible within a single module. Fixes #7139 Closes #7147 --- src/auto/injector.js | 21 +++++++++++++-------- src/loader.js | 11 ++++++++--- test/auto/injectorSpec.js | 23 +++++++++++++++++++++++ test/loaderSpec.js | 4 +++- 4 files changed, 47 insertions(+), 12 deletions(-) diff --git a/src/auto/injector.js b/src/auto/injector.js index 625d0ac820d..5cfd4bb2430 100644 --- a/src/auto/injector.js +++ b/src/auto/injector.js @@ -693,22 +693,27 @@ function createInjector(modulesToLoad, strictDi) { // Module Loading //////////////////////////////////// function loadModules(modulesToLoad){ - var runBlocks = [], moduleFn, invokeQueue, i, ii; + var runBlocks = [], moduleFn, invokeQueue; forEach(modulesToLoad, function(module) { if (loadedModules.get(module)) return; loadedModules.put(module, true); + function runInvokeQueue(queue) { + var i, ii; + for(i = 0, ii = queue.length; i < ii; i++) { + var invokeArgs = queue[i], + provider = providerInjector.get(invokeArgs[0]); + + provider[invokeArgs[1]].apply(provider, invokeArgs[2]); + } + } + try { if (isString(module)) { moduleFn = angularModule(module); runBlocks = runBlocks.concat(loadModules(moduleFn.requires)).concat(moduleFn._runBlocks); - - for(invokeQueue = moduleFn._invokeQueue, i = 0, ii = invokeQueue.length; i < ii; i++) { - var invokeArgs = invokeQueue[i], - provider = providerInjector.get(invokeArgs[0]); - - provider[invokeArgs[1]].apply(provider, invokeArgs[2]); - } + runInvokeQueue(moduleFn._invokeQueue); + runInvokeQueue(moduleFn._configBlocks); } else if (isFunction(module)) { runBlocks.push(providerInjector.invoke(module)); } else if (isArray(module)) { diff --git a/src/loader.js b/src/loader.js index dee02f0b9e7..f81a4d3289c 100644 --- a/src/loader.js +++ b/src/loader.js @@ -99,15 +99,19 @@ function setupModuleLoader(window) { /** @type {!Array.>} */ var invokeQueue = []; + /** @type {!Array.} */ + var configBlocks = []; + /** @type {!Array.} */ var runBlocks = []; - var config = invokeLater('$injector', 'invoke'); + var config = invokeLater('$injector', 'invoke', 'push', configBlocks); /** @type {angular.Module} */ var moduleInstance = { // Private state _invokeQueue: invokeQueue, + _configBlocks: configBlocks, _runBlocks: runBlocks, /** @@ -299,9 +303,10 @@ function setupModuleLoader(window) { * @param {String=} insertMethod * @returns {angular.Module} */ - function invokeLater(provider, method, insertMethod) { + function invokeLater(provider, method, insertMethod, queue) { + if (!queue) queue = invokeQueue; return function() { - invokeQueue[insertMethod || 'push']([provider, method, arguments]); + queue[insertMethod || 'push']([provider, method, arguments]); return moduleInstance; }; } diff --git a/test/auto/injectorSpec.js b/test/auto/injectorSpec.js index eecde9bf33b..3b3fbeec143 100644 --- a/test/auto/injectorSpec.js +++ b/test/auto/injectorSpec.js @@ -308,6 +308,29 @@ describe('injector', function() { expect(log).toEqual('abABCD'); }); + it('should execute own config blocks after all own providers are invoked', function() { + var log = ''; + angular.module('a', ['b']) + .config(function($aProvider) { + log += 'aConfig;'; + }) + .provider('$a', function() { + log += '$aProvider;'; + this.$get = function() {}; + }); + angular.module('b', []) + .config(function($bProvider) { + log += 'bConfig;'; + }) + .provider('$b', function() { + log += '$bProvider;'; + this.$get = function() {}; + }); + + createInjector(['a']); + expect(log).toBe('$bProvider;bConfig;$aProvider;aConfig;'); + }); + describe('$provide', function() { it('should throw an exception if we try to register a service called "hasOwnProperty"', function() { diff --git a/test/loaderSpec.js b/test/loaderSpec.js index e17f329f32e..dbe63726978 100644 --- a/test/loaderSpec.js +++ b/test/loaderSpec.js @@ -46,7 +46,6 @@ describe('module loader', function() { expect(myModule.requires).toEqual(['other']); expect(myModule._invokeQueue).toEqual([ ['$provide', 'constant', ['abc', 123] ], - ['$injector', 'invoke', ['config'] ], ['$provide', 'provider', ['sk', 'sv'] ], ['$provide', 'factory', ['fk', 'fv'] ], ['$provide', 'service', ['a', 'aa'] ], @@ -54,6 +53,9 @@ describe('module loader', function() { ['$filterProvider', 'register', ['f', 'ff'] ], ['$compileProvider', 'directive', ['d', 'dd'] ], ['$controllerProvider', 'register', ['ctrl', 'ccc']], + ]); + expect(myModule._configBlocks).toEqual([ + ['$injector', 'invoke', ['config'] ], ['$injector', 'invoke', ['init2'] ] ]); expect(myModule._runBlocks).toEqual(['runBlock']);