Permalink
Comparing changes
Open a pull request
- 3 commits
- 3 files changed
- 0 commit comments
- 2 contributors
Commits on May 24, 2016
`module.decorator` is now processed via the configBlocks order of operations and:
1. no longer throws error if declared before the provider being decorated.
2. guarantees the correct provider will be decorated when multiple, same-name
providers are defined.
(1) Prior to this fix, declaring `module.decorator` before the provider that it
decorates results in throwing an error:
```js
angular
.module('theApp', [])
.decorator('theFactory', moduleDecoratorFn)
.factory('theFactory', theFactoryFn);
// Error: [$injector:modulerr] Failed to instantiate module theApp due to:
// Error: [$injector:unpr] Unknown provider: theFactoryProvider
```
The result of this fix now allows for the declaration order above.
(2) Prior to this fix, declaring `module.decorator` before the final, same-named
provider results in that provider **not** being decorated as expected:
**NOTE:** Angular does not use provider name spacing, so the final declared
provider is selected if multiple, same-named providers are declared.
```js
angular
.module('theApp', [])
.factory('theFactory', theFactoryFn)
.decorator('theFactory', moduleDecoratorFn)
.factory('theFactory', theOtherFactoryFn);
```
`theOtherFactoryFn` is selected as 'theFactory' provider, but prior to this fix it is **not**
decorated via `moduleDecoratorFn`. This fix ensures that `theOtherFactoryFn` will be decorated as
expected when using the declaration order above.
Closes #12382
Closes #14348
BREAKING CHANGE:
`module.decorator` declarations are now processed as part of the `module.config`
queue and may result in providers being decorated in a different order if
`module.config` blocks are also used to decorate providers via
`$provide.decorator`.
For example, consider the following declaration order in which 'theFactory' is
decorated by both a `module.decorator` and a `$provide.decorator`:
```js
angular
.module('theApp', [])
.factory('theFactory', theFactoryFn)
.config(function($provide) {
$provide.decorator('theFactory', provideDecoratorFn);
})
.decorator('theFactory', moduleDecoratorFn);
```
Prior to this fix, 'theFactory' provider would be decorated in the following
order:
1. moduleDecoratorFn
2. provideDecoratorFn
The result of this fix changes the order in which 'theFactory' is decorated
because now `module.decorator` declarations are processed in the same order as
`module.config` declarations:
1. provideDecoratorFn
2. moduleDecoratorFn
Unified
Split
Showing
with
95 additions
and 13 deletions.
- +27 −9 docs/content/guide/decorators.ngdoc
- +4 −3 src/loader.js
- +64 −1 test/loaderSpec.js
| @@ -159,19 +159,37 @@ the end of the name. The `$delegate` provided is dictated by the type of service | ||
| ### module.decorator | ||
|
|
||
| This {@link api/ng/type/angular.Module#decorator function} is the same as the `$provide.decorator` function except it is | ||
| exposed through the module API. This allows you to separate your decorator patterns from your module config blocks. The | ||
| main caveat here is that you will need to take note the order in which you create your decorators. | ||
| exposed through the module API. This allows you to separate your decorator patterns from your module config blocks. | ||
|
|
||
| Unlike in the module config block (which allows configuration of services prior to their creation), the service must be | ||
| registered prior to the decorator (see {@link guide/providers#provider-recipe Provider Recipe}). For example, the | ||
| following would not work because you are attempting to decorate outside of the configuration phase and the service | ||
| hasn't been created yet: | ||
| Like with `$provide.decorator`, the `module.decorator` function runs during the config phase of the app. That means | ||
| you can define a `module.decorator` before the decorated service is defined. | ||
|
|
||
| Since you can apply multiple decorators, it is noteworthy that decorator application always follows order | ||
| of declaration: | ||
|
|
||
| - If a service is decorated by both `$provide.decorator` and `module.decorator`, the decorators are applied in order: | ||
|
|
||
| ```js | ||
| angular | ||
| .module('theApp', []) | ||
| .factory('theFactory', theFactoryFn) | ||
| .config(function($provide) { | ||
| $provide.decorator('theFactory', provideDecoratorFn); // runs first | ||
| }) | ||
| .decorator('theFactory', moduleDecoratorFn); // runs seconds | ||
| ``` | ||
|
|
||
| - If the service has been declared multiple times, a decorator will decorate the service that has been declared | ||
| last: | ||
|
|
||
| ```js | ||
| // will cause an error since 'someService' hasn't been registered | ||
| angular.module('myApp').decorator('someService', ...); | ||
| angular | ||
| .module('theApp', []) | ||
| .factory('theFactory', theFactoryFn) | ||
| .decorator('theFactory', moduleDecoratorFn) | ||
| .factory('theFactory', theOtherFactoryFn); | ||
|
|
||
| angular.module('myApp').factory('someService', ...); | ||
| // `theOtherFactoryFn` is selected as 'theFactory' provider and it is decorated via `moduleDecoratorFn`. | ||
| ``` | ||
|
|
||
| ## Example Applications | ||
| @@ -203,7 +203,7 @@ function setupModuleLoader(window) { | ||
| * @description | ||
| * See {@link auto.$provide#decorator $provide.decorator()}. | ||
| */ | ||
| decorator: invokeLaterAndSetModuleName('$provide', 'decorator'), | ||
| decorator: invokeLaterAndSetModuleName('$provide', 'decorator', configBlocks), | ||
|
|
||
| /** | ||
| * @ngdoc method | ||
| @@ -349,10 +349,11 @@ function setupModuleLoader(window) { | ||
| * @param {string} method | ||
| * @returns {angular.Module} | ||
| */ | ||
| function invokeLaterAndSetModuleName(provider, method) { | ||
| function invokeLaterAndSetModuleName(provider, method, queue) { | ||
| if (!queue) queue = invokeQueue; | ||
| return function(recipeName, factoryFunction) { | ||
| if (factoryFunction && isFunction(factoryFunction)) factoryFunction.$$moduleName = name; | ||
| invokeQueue.push([provider, method, arguments]); | ||
| queue.push([provider, method, arguments]); | ||
| return moduleInstance; | ||
| }; | ||
| } | ||
| @@ -48,7 +48,6 @@ describe('module loader', function() { | ||
| expect(myModule.requires).toEqual(['other']); | ||
| expect(myModule._invokeQueue).toEqual([ | ||
| ['$provide', 'constant', jasmine.objectContaining(['abc', 123])], | ||
| ['$provide', 'decorator', jasmine.objectContaining(['dk', 'dv'])], | ||
| ['$provide', 'provider', jasmine.objectContaining(['sk', 'sv'])], | ||
| ['$provide', 'factory', jasmine.objectContaining(['fk', 'fv'])], | ||
| ['$provide', 'service', jasmine.objectContaining(['a', 'aa'])], | ||
| @@ -60,12 +59,76 @@ describe('module loader', function() { | ||
| ]); | ||
| expect(myModule._configBlocks).toEqual([ | ||
| ['$injector', 'invoke', jasmine.objectContaining(['config'])], | ||
| ['$provide', 'decorator', jasmine.objectContaining(['dk', 'dv'])], | ||
| ['$injector', 'invoke', jasmine.objectContaining(['init2'])] | ||
| ]); | ||
| expect(myModule._runBlocks).toEqual(['runBlock']); | ||
| }); | ||
|
|
||
|
|
||
| it("should not throw error when `module.decorator` is declared before provider that it decorates", function() { | ||
| angular.module('theModule', []). | ||
| decorator('theProvider', function($delegate) { return $delegate; }). | ||
| factory('theProvider', function() { return {}; }); | ||
|
|
||
| expect(function() { | ||
| createInjector(['theModule']); | ||
| }).not.toThrow(); | ||
| }); | ||
|
|
||
|
|
||
| it("should run decorators in order of declaration, even when mixed with provider.decorator", function() { | ||
| var log = ''; | ||
|
|
||
| angular.module('theModule', []) | ||
| .factory('theProvider', function() { | ||
| return {api: 'provider'}; | ||
| }) | ||
| .decorator('theProvider', function($delegate) { | ||
| $delegate.api = $delegate.api + '-first'; | ||
| return $delegate; | ||
| }) | ||
| .config(function($provide) { | ||
| $provide.decorator('theProvider', function($delegate) { | ||
| $delegate.api = $delegate.api + '-second'; | ||
| return $delegate; | ||
| }); | ||
| }) | ||
| .decorator('theProvider', function($delegate) { | ||
| $delegate.api = $delegate.api + '-third'; | ||
| return $delegate; | ||
| }) | ||
| .run(function(theProvider) { | ||
| log = theProvider.api; | ||
| }) | ||
|
|
||
| createInjector(['theModule']); | ||
| expect(log).toBe('provider-first-second-third'); | ||
| }); | ||
|
|
||
|
|
||
| it("should decorate the last declared provider if multiple have been declared", function() { | ||
| var log = ''; | ||
|
|
||
| angular.module('theModule', []). | ||
| factory('theProvider', function() { return { | ||
| api: 'firstProvider' | ||
| }; }). | ||
| decorator('theProvider', function($delegate) { | ||
| $delegate.api = $delegate.api + '-decorator'; | ||
| return $delegate; }). | ||
| factory('theProvider', function() { return { | ||
| api: 'secondProvider' | ||
| }; }). | ||
| run(function(theProvider) { | ||
| log = theProvider.api; | ||
| }); | ||
|
|
||
| createInjector(['theModule']); | ||
| expect(log).toBe('secondProvider-decorator') | ||
| }); | ||
|
|
||
|
|
||
| it('should allow module redefinition', function() { | ||
| expect(window.angular.module('a', [])).not.toBe(window.angular.module('a', [])); | ||
| }); | ||