Permalink
Browse files

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
  • Loading branch information...
1 parent 924ee6d commit c0b4e2db9cbc8bc3164cedc4646145d3ab72536e @caitp caitp committed Apr 17, 2014
Showing with 47 additions and 12 deletions.
  1. +13 −8 src/auto/injector.js
  2. +8 −3 src/loader.js
  3. +23 −0 test/auto/injectorSpec.js
  4. +3 −1 test/loaderSpec.js
View
@@ -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)) {
View
@@ -100,14 +100,18 @@ function setupModuleLoader(window) {
var invokeQueue = [];
/** @type {!Array.<Function>} */
+ var configBlocks = [];
+
+ /** @type {!Array.<Function>} */
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;
};
}
@@ -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() {
View
@@ -46,14 +46,16 @@ 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'] ],
['$provide', 'value', ['k', 'v'] ],
['$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']);

0 comments on commit c0b4e2d

Please sign in to comment.