fix(ivy): module with providers are processed too early (#30688)

Currently with Ivy, `ModuleWithProvider` providers are processed in order
of declaration in the `NgModule` imports. This technically makes makes
sense but is a potential breaking change as `ModuleWithProvider` providers
are processed after all imported modules in View Engine.

In order to keep the behavior of View Engine, the `r3_injector` is updated
to no longer process `ModuleWithProvider` providers egarly.

Resolves FW-1349

PR Close #30688
This commit is contained in:
Paul Gschwendtner
2019-05-27 20:34:01 +02:00
committed by Miško Hevery
parent 1f79c827a0
commit 6d861f240b
5 changed files with 156 additions and 16 deletions

View File

@ -222,14 +222,20 @@ export class R3Injector {
}
/**
* Add an `InjectorType` or `InjectorDefTypeWithProviders` and all of its transitive providers
* Add an `InjectorType` or `InjectorTypeWithProviders` and all of its transitive providers
* to this injector.
*
* If an `InjectorTypeWithProviders` that declares providers besides the type is specified,
* the function will return "true" to indicate that the providers of the type definition need
* to be processed. This allows us to process providers of injector types after all imports of
* an injector definition are processed. (following View Engine semantics: see FW-1349)
*/
private processInjectorType(
defOrWrappedDef: InjectorType<any>|InjectorTypeWithProviders<any>,
parents: InjectorType<any>[], dedupStack: InjectorType<any>[]) {
parents: InjectorType<any>[],
dedupStack: InjectorType<any>[]): defOrWrappedDef is InjectorTypeWithProviders<any> {
defOrWrappedDef = resolveForwardRef(defOrWrappedDef);
if (!defOrWrappedDef) return;
if (!defOrWrappedDef) return false;
// Either the defOrWrappedDef is an InjectorType (with ngInjectorDef) or an
// InjectorDefTypeWithProviders (aka ModuleWithProviders). Detecting either is a megamorphic
@ -259,12 +265,6 @@ export class R3Injector {
// Check for multiple imports of the same module
const isDuplicate = dedupStack.indexOf(defType) !== -1;
// If defOrWrappedType was an InjectorDefTypeWithProviders, then .providers may hold some
// extra providers.
const providers =
(ngModule !== undefined) && (defOrWrappedDef as InjectorTypeWithProviders<any>).providers ||
EMPTY_ARRAY;
// Finally, if defOrWrappedType was an `InjectorDefTypeWithProviders`, then the actual
// `InjectorDef` is on its `ngModule`.
if (ngModule !== undefined) {
@ -273,7 +273,7 @@ export class R3Injector {
// If no definition was found, it might be from exports. Remove it.
if (def == null) {
return;
return false;
}
// Track the InjectorType and add a provider for it.
@ -291,14 +291,33 @@ export class R3Injector {
// Add it to the set of dedups. This way we can detect multiple imports of the same module
dedupStack.push(defType);
let importTypesWithProviders: (InjectorTypeWithProviders<any>[])|undefined;
try {
deepForEach(
def.imports, imported => this.processInjectorType(imported, parents, dedupStack));
deepForEach(def.imports, imported => {
if (this.processInjectorType(imported, parents, dedupStack)) {
if (importTypesWithProviders === undefined) importTypesWithProviders = [];
// If the processed import is an injector type with providers, we store it in the
// list of import types with providers, so that we can process those afterwards.
importTypesWithProviders.push(imported);
}
});
} finally {
// Remove it from the parents set when finished.
// TODO(FW-1307): Re-add ngDevMode when closure can handle it
parents.pop();
}
// Imports which are declared with providers (TypeWithProviders) need to be processed
// after all imported modules are processed. This is similar to how View Engine
// processes/merges module imports in the metadata resolver. See: FW-1349.
if (importTypesWithProviders !== undefined) {
for (let i = 0; i < importTypesWithProviders.length; i++) {
const {ngModule, providers} = importTypesWithProviders[i];
deepForEach(
providers !,
provider => this.processProvider(provider, ngModule, providers || EMPTY_ARRAY));
}
}
}
// Next, include providers listed on the definition itself.
@ -309,9 +328,9 @@ export class R3Injector {
defProviders, provider => this.processProvider(provider, injectorType, defProviders));
}
// Finally, include providers from an InjectorDefTypeWithProviders if there was one.
const ngModuleType = (defOrWrappedDef as InjectorTypeWithProviders<any>).ngModule;
deepForEach(providers, provider => this.processProvider(provider, ngModuleType, providers));
return (
ngModule !== undefined &&
(defOrWrappedDef as InjectorTypeWithProviders<any>).providers !== undefined);
}
/**