diff --git a/packages/core/src/di/provider_collection.ts b/packages/core/src/di/provider_collection.ts index 5151a86baf8d3..bfccf84cdebba 100644 --- a/packages/core/src/di/provider_collection.ts +++ b/packages/core/src/di/provider_collection.ts @@ -21,7 +21,7 @@ import {resolveForwardRef} from './forward_ref'; import {ENVIRONMENT_INITIALIZER} from './initializer_token'; import {ɵɵinject as inject} from './injector_compatibility'; import {getInjectorDef, InjectorType, InjectorTypeWithProviders} from './interface/defs'; -import {ClassProvider, ConstructorProvider, EnvironmentProviders, ExistingProvider, FactoryProvider, ImportedNgModuleProviders, ModuleWithProviders, Provider, StaticClassProvider, TypeProvider, ValueProvider} from './interface/provider'; +import {ClassProvider, ConstructorProvider, EnvironmentProviders, ExistingProvider, FactoryProvider, ImportedNgModuleProviders, InternalEnvironmentProviders, isEnvironmentProviders, ModuleWithProviders, Provider, StaticClassProvider, TypeProvider, ValueProvider} from './interface/provider'; import {INJECTOR_DEF_TYPES} from './internal_tokens'; /** @@ -128,7 +128,7 @@ function processInjectorTypesWithProviders( typesWithProviders: InjectorTypeWithProviders[], providersOut: Provider[]): void { for (let i = 0; i < typesWithProviders.length; i++) { const {ngModule, providers} = typesWithProviders[i]; - deepForEach(providers!, provider => { + deepForEachProvider(providers! as Array, provider => { ngDevMode && validateProvider(provider, providers || EMPTY_ARRAY, ngModule); providersOut.push(provider); }); @@ -261,12 +261,12 @@ export function walkProviderTree( } // Next, include providers listed on the definition itself. - const defProviders = injDef.providers; + const defProviders = injDef.providers as Array; if (defProviders != null && !isDuplicate) { const injectorType = container as InjectorType; - deepForEach(defProviders, provider => { - ngDevMode && validateProvider(provider, defProviders as SingleProvider[], injectorType); - providersOut.push(provider); + deepForEachProvider(defProviders, provider => { + ngDevMode && validateProvider(provider as SingleProvider, defProviders, injectorType); + providersOut.push(provider as SingleProvider); }); } } else { @@ -280,7 +280,8 @@ export function walkProviderTree( } function validateProvider( - provider: SingleProvider, providers: SingleProvider[], containerType: Type): void { + provider: SingleProvider, providers: Array, + containerType: Type): void { if (isTypeProvider(provider) || isValueProvider(provider) || isFactoryProvider(provider) || isExistingProvider(provider)) { return; @@ -294,6 +295,21 @@ function validateProvider( } } +function deepForEachProvider( + providers: Array, + fn: (provider: SingleProvider) => void): void { + for (let provider of providers) { + if (isEnvironmentProviders(provider)) { + provider = provider.ɵproviders; + } + if (Array.isArray(provider)) { + deepForEachProvider(provider, fn); + } else { + fn(provider); + } + } +} + export const USE_VALUE = getClosureSafeProperty({provide: String, useValue: getClosureSafeProperty}); diff --git a/packages/core/src/di/r3_injector.ts b/packages/core/src/di/r3_injector.ts index b175be228160b..df67dd2fc34b5 100644 --- a/packages/core/src/di/r3_injector.ts +++ b/packages/core/src/di/r3_injector.ts @@ -27,7 +27,7 @@ import {catchInjectorError, convertToBitFlags, injectArgs, NG_TEMP_TOKEN_PATH, s import {INJECTOR} from './injector_token'; import {getInheritedInjectableDef, getInjectableDef, InjectorType, ɵɵInjectableDeclaration} from './interface/defs'; import {InjectFlags, InjectOptions} from './interface/injector'; -import {ClassProvider, ConstructorProvider, ImportedNgModuleProviders, Provider, StaticClassProvider} from './interface/provider'; +import {ClassProvider, ConstructorProvider, EnvironmentProviders, ImportedNgModuleProviders, InternalEnvironmentProviders, isEnvironmentProviders, Provider, StaticClassProvider} from './interface/provider'; import {INJECTOR_DEF_TYPES} from './internal_tokens'; import {NullInjector} from './null_injector'; import {isExistingProvider, isFactoryProvider, isTypeProvider, isValueProvider, SingleProvider} from './provider_collection'; @@ -157,11 +157,14 @@ export class R3Injector extends EnvironmentInjector { private injectorDefTypes: Set>; constructor( - providers: Array, readonly parent: Injector, - readonly source: string|null, readonly scopes: Set) { + providers: Array, + readonly parent: Injector, readonly source: string|null, + readonly scopes: Set) { super(); // Start off by creating Records for every provider. - forEachSingleProvider(providers, provider => this.processProvider(provider)); + forEachSingleProvider( + providers as Array, + provider => this.processProvider(provider)); // Make sure the INJECTOR token provides this injector. this.records.set(INJECTOR, makeRecord(undefined, this)); @@ -460,7 +463,7 @@ function providerToRecord(provider: SingleProvider): Record { export function providerToFactory( provider: SingleProvider, ngModuleType?: InjectorType, providers?: any[]): () => any { let factory: (() => any)|undefined = undefined; - if (ngDevMode && isImportedNgModuleProviders(provider)) { + if (ngDevMode && (isImportedNgModuleProviders(provider) || isEnvironmentProviders(provider))) { throwInvalidProviderError(undefined, providers, provider); } @@ -517,19 +520,20 @@ function couldBeInjectableType(value: any): value is ProviderToken { function isImportedNgModuleProviders(provider: Provider|ImportedNgModuleProviders): provider is ImportedNgModuleProviders { - return !!(provider as ImportedNgModuleProviders).ɵproviders; + return provider && !!(provider as ImportedNgModuleProviders).ɵproviders; } function forEachSingleProvider( - providers: Array, + providers: Array, fn: (provider: SingleProvider) => void): void { for (const provider of providers) { if (Array.isArray(provider)) { forEachSingleProvider(provider, fn); - } else if (isImportedNgModuleProviders(provider)) { + } else if ( + provider && (isImportedNgModuleProviders(provider) || isEnvironmentProviders(provider))) { forEachSingleProvider(provider.ɵproviders, fn); } else { - fn(provider); + fn(provider as SingleProvider); } } } diff --git a/packages/core/src/render3/errors_di.ts b/packages/core/src/render3/errors_di.ts index 5811435154498..b24bb2601675d 100644 --- a/packages/core/src/render3/errors_di.ts +++ b/packages/core/src/render3/errors_di.ts @@ -6,7 +6,7 @@ * found in the LICENSE file at https://angular.io/license */ -import {ImportedNgModuleProviders} from '../di/interface/provider'; +import {isEnvironmentProviders} from '../di/interface/provider'; import {RuntimeError, RuntimeErrorCode} from '../errors'; import {Type} from '../interface/type'; import {stringify} from '../util/stringify'; @@ -33,10 +33,16 @@ export function throwInvalidProviderError( throw new Error(`Invalid provider for the NgModule '${ stringify(ngModuleType)}' - only instances of Provider and Type are allowed, got: [${ providerDetail.join(', ')}]`); - } else if ((provider as ImportedNgModuleProviders).ɵproviders) { - throw new RuntimeError( - RuntimeErrorCode.PROVIDER_IN_WRONG_CONTEXT, - `Invalid providers from 'importProvidersFrom' present in a non-environment injector. 'importProvidersFrom' can't be used for component providers.`); + } else if (isEnvironmentProviders(provider)) { + if (provider.ɵfromNgModule) { + throw new RuntimeError( + RuntimeErrorCode.PROVIDER_IN_WRONG_CONTEXT, + `Invalid providers from 'importProvidersFrom' present in a non-environment injector. 'importProvidersFrom' can't be used for component providers.`); + } else { + throw new RuntimeError( + RuntimeErrorCode.PROVIDER_IN_WRONG_CONTEXT, + `Invalid providers present in a non-environment injector. 'EnvironmentProviders' can't be used for component providers.`); + } } else { throw new Error('Invalid provider'); } diff --git a/packages/core/test/bundling/animations/bundle.golden_symbols.json b/packages/core/test/bundling/animations/bundle.golden_symbols.json index 547c00771d376..315bac4c8cc6f 100644 --- a/packages/core/test/bundling/animations/bundle.golden_symbols.json +++ b/packages/core/test/bundling/animations/bundle.golden_symbols.json @@ -710,6 +710,9 @@ { "name": "deepForEach" }, + { + "name": "deepForEachProvider" + }, { "name": "detachMovedView" }, @@ -1001,6 +1004,9 @@ { "name": "isElementNode" }, + { + "name": "isEnvironmentProviders" + }, { "name": "isFunction" }, diff --git a/packages/core/test/bundling/cyclic_import/bundle.golden_symbols.json b/packages/core/test/bundling/cyclic_import/bundle.golden_symbols.json index 901b1c97a972c..5ea4235ef520e 100644 --- a/packages/core/test/bundling/cyclic_import/bundle.golden_symbols.json +++ b/packages/core/test/bundling/cyclic_import/bundle.golden_symbols.json @@ -497,6 +497,9 @@ { "name": "deepForEach" }, + { + "name": "deepForEachProvider" + }, { "name": "detachMovedView" }, @@ -749,6 +752,9 @@ { "name": "isCurrentTNodeParent" }, + { + "name": "isEnvironmentProviders" + }, { "name": "isFunction" }, diff --git a/packages/core/test/bundling/forms_reactive/bundle.golden_symbols.json b/packages/core/test/bundling/forms_reactive/bundle.golden_symbols.json index cc34b5820d893..3318dd0dd5d44 100644 --- a/packages/core/test/bundling/forms_reactive/bundle.golden_symbols.json +++ b/packages/core/test/bundling/forms_reactive/bundle.golden_symbols.json @@ -722,6 +722,9 @@ { "name": "deepForEach" }, + { + "name": "deepForEachProvider" + }, { "name": "defaultIterableDiffersFactory" }, @@ -1097,6 +1100,9 @@ { "name": "isEmptyInputValue" }, + { + "name": "isEnvironmentProviders" + }, { "name": "isFormControlState" }, diff --git a/packages/core/test/bundling/forms_template_driven/bundle.golden_symbols.json b/packages/core/test/bundling/forms_template_driven/bundle.golden_symbols.json index 24014002c6e70..dbf07f3c1eb76 100644 --- a/packages/core/test/bundling/forms_template_driven/bundle.golden_symbols.json +++ b/packages/core/test/bundling/forms_template_driven/bundle.golden_symbols.json @@ -692,6 +692,9 @@ { "name": "deepForEach" }, + { + "name": "deepForEachProvider" + }, { "name": "defaultIterableDiffersFactory" }, @@ -1055,6 +1058,9 @@ { "name": "isDirectiveHost" }, + { + "name": "isEnvironmentProviders" + }, { "name": "isFormControlState" }, diff --git a/packages/core/test/bundling/hello_world/bundle.golden_symbols.json b/packages/core/test/bundling/hello_world/bundle.golden_symbols.json index 0aaeb336a1f53..9ea32f8b839e8 100644 --- a/packages/core/test/bundling/hello_world/bundle.golden_symbols.json +++ b/packages/core/test/bundling/hello_world/bundle.golden_symbols.json @@ -359,6 +359,9 @@ { "name": "deepForEach" }, + { + "name": "deepForEachProvider" + }, { "name": "detachMovedView" }, @@ -557,6 +560,9 @@ { "name": "isComponentDef" }, + { + "name": "isEnvironmentProviders" + }, { "name": "isFunction" }, diff --git a/packages/core/test/bundling/injection/bundle.golden_symbols.json b/packages/core/test/bundling/injection/bundle.golden_symbols.json index 824d7bd0646e7..2d570fd5ad72c 100644 --- a/packages/core/test/bundling/injection/bundle.golden_symbols.json +++ b/packages/core/test/bundling/injection/bundle.golden_symbols.json @@ -83,6 +83,9 @@ { "name": "deepForEach" }, + { + "name": "deepForEachProvider" + }, { "name": "forEachSingleProvider" }, @@ -122,6 +125,9 @@ { "name": "internalImportProvidersFrom" }, + { + "name": "isEnvironmentProviders" + }, { "name": "isImportedNgModuleProviders" }, diff --git a/packages/core/test/bundling/router/bundle.golden_symbols.json b/packages/core/test/bundling/router/bundle.golden_symbols.json index 7a96b86812794..a22647464690e 100644 --- a/packages/core/test/bundling/router/bundle.golden_symbols.json +++ b/packages/core/test/bundling/router/bundle.golden_symbols.json @@ -884,6 +884,9 @@ { "name": "deepForEach" }, + { + "name": "deepForEachProvider" + }, { "name": "defaultErrorFactory" }, @@ -1340,6 +1343,9 @@ { "name": "isEmptyError" }, + { + "name": "isEnvironmentProviders" + }, { "name": "isFunction" }, diff --git a/packages/core/test/bundling/standalone_bootstrap/bundle.golden_symbols.json b/packages/core/test/bundling/standalone_bootstrap/bundle.golden_symbols.json index e6cc180981bef..9b2948504fb41 100644 --- a/packages/core/test/bundling/standalone_bootstrap/bundle.golden_symbols.json +++ b/packages/core/test/bundling/standalone_bootstrap/bundle.golden_symbols.json @@ -428,6 +428,9 @@ { "name": "deepForEach" }, + { + "name": "deepForEachProvider" + }, { "name": "detachMovedView" }, @@ -641,6 +644,9 @@ { "name": "isComponentDef" }, + { + "name": "isEnvironmentProviders" + }, { "name": "isFunction" }, diff --git a/packages/core/test/bundling/todo/bundle.golden_symbols.json b/packages/core/test/bundling/todo/bundle.golden_symbols.json index be63cdaaa1e04..525cfb713254f 100644 --- a/packages/core/test/bundling/todo/bundle.golden_symbols.json +++ b/packages/core/test/bundling/todo/bundle.golden_symbols.json @@ -602,6 +602,9 @@ { "name": "deepForEach" }, + { + "name": "deepForEachProvider" + }, { "name": "defaultIterableDiffersFactory" }, @@ -926,6 +929,9 @@ { "name": "isDirectiveHost" }, + { + "name": "isEnvironmentProviders" + }, { "name": "isFunction" }, diff --git a/packages/core/testing/src/test_bed_compiler.ts b/packages/core/testing/src/test_bed_compiler.ts index 57cd4b3f16fb7..f526c1d201339 100644 --- a/packages/core/testing/src/test_bed_compiler.ts +++ b/packages/core/testing/src/test_bed_compiler.ts @@ -7,7 +7,7 @@ */ import {ResourceLoader} from '@angular/compiler'; -import {ApplicationInitStatus, Compiler, COMPILER_OPTIONS, Component, Directive, Injector, InjectorType, LOCALE_ID, ModuleWithComponentFactories, ModuleWithProviders, NgModule, NgModuleFactory, NgZone, Pipe, PlatformRef, Provider, resolveForwardRef, Type, ɵcompileComponent as compileComponent, ɵcompileDirective as compileDirective, ɵcompileNgModuleDefs as compileNgModuleDefs, ɵcompilePipe as compilePipe, ɵDEFAULT_LOCALE_ID as DEFAULT_LOCALE_ID, ɵDirectiveDef as DirectiveDef, ɵgetInjectableDef as getInjectableDef, ɵNG_COMP_DEF as NG_COMP_DEF, ɵNG_DIR_DEF as NG_DIR_DEF, ɵNG_INJ_DEF as NG_INJ_DEF, ɵNG_MOD_DEF as NG_MOD_DEF, ɵNG_PIPE_DEF as NG_PIPE_DEF, ɵNgModuleFactory as R3NgModuleFactory, ɵNgModuleTransitiveScopes as NgModuleTransitiveScopes, ɵNgModuleType as NgModuleType, ɵpatchComponentDefWithScope as patchComponentDefWithScope, ɵRender3ComponentFactory as ComponentFactory, ɵRender3NgModuleRef as NgModuleRef, ɵsetLocaleId as setLocaleId, ɵtransitiveScopesFor as transitiveScopesFor, ɵɵInjectableDeclaration as InjectableDeclaration} from '@angular/core'; +import {ApplicationInitStatus, Compiler, COMPILER_OPTIONS, Component, Directive, Injector, InjectorType, LOCALE_ID, ModuleWithComponentFactories, ModuleWithProviders, NgModule, NgModuleFactory, NgZone, Pipe, PlatformRef, Provider, resolveForwardRef, Type, ɵcompileComponent as compileComponent, ɵcompileDirective as compileDirective, ɵcompileNgModuleDefs as compileNgModuleDefs, ɵcompilePipe as compilePipe, ɵDEFAULT_LOCALE_ID as DEFAULT_LOCALE_ID, ɵDirectiveDef as DirectiveDef, ɵgetInjectableDef as getInjectableDef, ɵInternalEnvironmentProviders as InternalEnvironmentProviders, ɵisEnvironmentProviders as isEnvironmentProviders, ɵNG_COMP_DEF as NG_COMP_DEF, ɵNG_DIR_DEF as NG_DIR_DEF, ɵNG_INJ_DEF as NG_INJ_DEF, ɵNG_MOD_DEF as NG_MOD_DEF, ɵNG_PIPE_DEF as NG_PIPE_DEF, ɵNgModuleFactory as R3NgModuleFactory, ɵNgModuleTransitiveScopes as NgModuleTransitiveScopes, ɵNgModuleType as NgModuleType, ɵpatchComponentDefWithScope as patchComponentDefWithScope, ɵRender3ComponentFactory as ComponentFactory, ɵRender3NgModuleRef as NgModuleRef, ɵsetLocaleId as setLocaleId, ɵtransitiveScopesFor as transitiveScopesFor, ɵɵInjectableDeclaration as InjectableDeclaration} from '@angular/core'; import {clearResolutionOfComponentResourcesQueue, isComponentDefPendingResolution, resolveComponentResources, restoreComponentResolutionQueue} from '../../src/metadata/resource_loading'; import {ComponentDef, ComponentType} from '../../src/render3'; @@ -470,7 +470,7 @@ export class TestBedCompiler { this.applyProviderOverridesInScope(dependency); } } else { - const providers = [ + const providers: Array = [ ...injectorDef.providers, ...(this.providerOverridesByModule.get(type as InjectorType) || []) ]; @@ -496,7 +496,8 @@ export class TestBedCompiler { fieldName: 'providers', originalValue: importedModule.providers }); - importedModule.providers = this.getOverriddenProviders(importedModule.providers); + importedModule.providers = this.getOverriddenProviders( + importedModule.providers as Array); } } } @@ -797,21 +798,23 @@ export class TestBedCompiler { return this.providerOverridesByToken.get(token) || null; } - private getProviderOverrides(providers?: Provider[]): Provider[] { + private getProviderOverrides(providers?: Array): + Provider[] { if (!providers || !providers.length || this.providerOverridesByToken.size === 0) return []; - // There are two flattening operations here. The inner flatten() operates on the metadata's - // providers and applies a mapping function which retrieves overrides for each incoming - // provider. The outer flatten() then flattens the produced overrides array. If this is not - // done, the array can contain other empty arrays (e.g. `[[], []]`) which leak into the + // There are two flattening operations here. The inner flattenProviders() operates on the + // metadata's providers and applies a mapping function which retrieves overrides for each + // incoming provider. The outer flatten() then flattens the produced overrides array. If this is + // not done, the array can contain other empty arrays (e.g. `[[], []]`) which leak into the // providers array and contaminate any error messages that might be generated. - return flatten(flatten( + return flatten(flattenProviders( providers, (provider: Provider) => this.getSingleProviderOverrides(provider) || [])); } - private getOverriddenProviders(providers?: Provider[]): Provider[] { + private getOverriddenProviders(providers?: Array): + Provider[] { if (!providers || !providers.length || this.providerOverridesByToken.size === 0) return []; - const flattenedProviders = flatten(providers); + const flattenedProviders = flattenProviders(providers); const overrides = this.getProviderOverrides(flattenedProviders); const overriddenProviders = [...flattenedProviders, ...overrides]; const final: Provider[] = []; @@ -838,7 +841,7 @@ export class TestBedCompiler { return final; } - private hasProviderOverrides(providers?: Provider[]): boolean { + private hasProviderOverrides(providers?: Array): boolean { return this.getProviderOverrides(providers).length > 0; } @@ -887,18 +890,42 @@ function maybeUnwrapFn(maybeFn: (() => T)|T): T { return maybeFn instanceof Function ? maybeFn() : maybeFn; } -function flatten(values: any[], mapFn?: (value: T) => any): T[] { +function flatten(values: any[]): T[] { const out: T[] = []; values.forEach(value => { if (Array.isArray(value)) { - out.push(...flatten(value, mapFn)); + out.push(...flatten(value)); } else { - out.push(mapFn ? mapFn(value) : value); + out.push(value); } }); return out; } +function identityFn(value: T): T { + return value; +} + +function flattenProviders( + providers: Array, mapFn: (provider: Provider) => T): T[]; +function flattenProviders(providers: Array): Provider[]; +function flattenProviders( + providers: Array, + mapFn: (provider: Provider) => any = identityFn): any[] { + const out: any[] = []; + for (let provider of providers) { + if (isEnvironmentProviders(provider)) { + provider = provider.ɵproviders; + } + if (Array.isArray(provider)) { + out.push(...flattenProviders(provider, mapFn)); + } else { + out.push(mapFn(provider)); + } + } + return out; +} + function getProviderField(provider: Provider, field: string) { return provider && typeof provider === 'object' && (provider as any)[field]; }