From d322052db3c66904010e03074b5c7b611e511b2d Mon Sep 17 00:00:00 2001 From: Alex Rickabaugh Date: Mon, 2 May 2022 07:40:44 -0700 Subject: [PATCH] refactor(core): guard against `importProvidersFrom` in components (#45838) `importProvidersFrom` provides a bridge from the world of NgModule-based DI configuration to the new, "standalone" world of direct providers and environment injectors. Early user feedback suggested some confusion around where this function was supposed to be used, particularly around importing NgModule-based providers into standalone component `providers` arrays, which is not the intended use. This confusion is exacerbated by the fact that due to the unified `Provider` type, this kind of misconfiguration was happily accepted by the type system. This commit changes the return type of `importProvidersFrom` to wrap the returned providers in an opaque type that prevents them from being used in component provider contexts. This, together with stronger documentation around the purpose and functionality of `importProvidersFrom`, should address some of the above confusion. PR Close #45838 --- goldens/public-api/core/errors.md | 2 + goldens/public-api/core/index.md | 10 ++++- goldens/public-api/platform-browser/index.md | 3 +- goldens/public-api/platform-server/index.md | 3 +- goldens/public-api/router/index.md | 3 +- packages/core/src/application_ref.ts | 4 +- packages/core/src/di/create_injector.ts | 4 +- packages/core/src/di/index.ts | 2 +- packages/core/src/di/interface/provider.ts | 17 +++++++++ packages/core/src/di/provider_collection.ts | 13 +++++-- packages/core/src/di/r3_injector.ts | 35 +++++++++++++---- packages/core/src/errors.ts | 1 + packages/core/src/render3/errors_di.ts | 18 +++++---- .../render3/features/standalone_feature.ts | 4 +- packages/core/src/render3/ng_module_ref.ts | 8 ++-- packages/core/test/acceptance/di_spec.ts | 38 +++++++++++++++---- .../env_injector_standalone_spec.ts | 6 +-- .../animations/bundle.golden_symbols.json | 9 +++-- .../forms_reactive/bundle.golden_symbols.json | 9 +++-- .../bundle.golden_symbols.json | 9 +++-- .../injection/bundle.golden_symbols.json | 5 ++- .../router/bundle.golden_symbols.json | 6 +++ .../bundle.golden_symbols.json | 9 +++-- packages/platform-browser/src/browser.ts | 4 +- packages/platform-server/src/utils.ts | 8 ++-- packages/router/src/models.ts | 4 +- 26 files changed, 170 insertions(+), 64 deletions(-) diff --git a/goldens/public-api/core/errors.md b/goldens/public-api/core/errors.md index 72a4f0ee91a..25acf3a8037 100644 --- a/goldens/public-api/core/errors.md +++ b/goldens/public-api/core/errors.md @@ -61,6 +61,8 @@ export const enum RuntimeErrorCode { // (undocumented) PLATFORM_NOT_FOUND = 401, // (undocumented) + PROVIDER_IN_WRONG_CONTEXT = 207, + // (undocumented) PROVIDER_NOT_FOUND = -201, // (undocumented) RECURSIVE_APPLICATION_REF_TICK = 101, diff --git a/goldens/public-api/core/index.md b/goldens/public-api/core/index.md index 28a12ac688d..a26561a7b88 100644 --- a/goldens/public-api/core/index.md +++ b/goldens/public-api/core/index.md @@ -289,7 +289,7 @@ export interface ContentChildrenDecorator { } // @public -export function createEnvironmentInjector(providers: Provider[], parent?: EnvironmentInjector | null, debugName?: string | null): EnvironmentInjector; +export function createEnvironmentInjector(providers: Array, parent?: EnvironmentInjector | null, debugName?: string | null): EnvironmentInjector; // @public export function createNgModuleRef(ngModule: Type, parentInjector?: Injector): NgModuleRef; @@ -578,7 +578,13 @@ export interface HostListenerDecorator { } // @public -export function importProvidersFrom(...sources: ImportProvidersSource[]): Provider[]; +export interface ImportedNgModuleProviders { + // (undocumented) + ɵproviders: Provider[]; +} + +// @public +export function importProvidersFrom(...sources: ImportProvidersSource[]): ImportedNgModuleProviders; // @public export type ImportProvidersSource = Type | ModuleWithProviders | Array; diff --git a/goldens/public-api/platform-browser/index.md b/goldens/public-api/platform-browser/index.md index f377e715add..19976664592 100644 --- a/goldens/public-api/platform-browser/index.md +++ b/goldens/public-api/platform-browser/index.md @@ -10,6 +10,7 @@ import { DebugElement } from '@angular/core'; import { DebugNode } from '@angular/core'; import * as i0 from '@angular/core'; import * as i1 from '@angular/common'; +import { ImportedNgModuleProviders } from '@angular/core'; import { InjectionToken } from '@angular/core'; import { ModuleWithProviders } from '@angular/core'; import { NgZone } from '@angular/core'; @@ -24,7 +25,7 @@ import { Version } from '@angular/core'; // @public export interface ApplicationConfig { - providers: Provider[]; + providers: Array; } // @public diff --git a/goldens/public-api/platform-server/index.md b/goldens/public-api/platform-server/index.md index 04eb04af626..83965839557 100644 --- a/goldens/public-api/platform-server/index.md +++ b/goldens/public-api/platform-server/index.md @@ -8,6 +8,7 @@ import * as i0 from '@angular/core'; import * as i1 from '@angular/common/http'; import * as i2 from '@angular/platform-browser/animations'; import * as i3 from '@angular/platform-browser'; +import { ImportedNgModuleProviders } from '@angular/core'; import { InjectionToken } from '@angular/core'; import { NgModuleFactory } from '@angular/core'; import { PlatformRef } from '@angular/core'; @@ -52,7 +53,7 @@ export function renderApplication(rootComponent: Type, options: { appId: string; document?: string; url?: string; - providers?: Provider[]; + providers?: Array; platformProviders?: Provider[]; }): Promise; diff --git a/goldens/public-api/router/index.md b/goldens/public-api/router/index.md index 7bc5910d9a4..dcd37d85803 100644 --- a/goldens/public-api/router/index.md +++ b/goldens/public-api/router/index.md @@ -13,6 +13,7 @@ import { ElementRef } from '@angular/core'; import { EnvironmentInjector } from '@angular/core'; import { EventEmitter } from '@angular/core'; import * as i0 from '@angular/core'; +import { ImportedNgModuleProviders } from '@angular/core'; import { InjectionToken } from '@angular/core'; import { Injector } from '@angular/core'; import { Location as Location_2 } from '@angular/common'; @@ -456,7 +457,7 @@ export interface Route { outlet?: string; path?: string; pathMatch?: 'prefix' | 'full'; - providers?: Provider[]; + providers?: Array; redirectTo?: string; resolve?: ResolveData; runGuardsAndResolvers?: RunGuardsAndResolvers; diff --git a/packages/core/src/application_ref.ts b/packages/core/src/application_ref.ts index 8a50e34f106..a5cba8a357a 100644 --- a/packages/core/src/application_ref.ts +++ b/packages/core/src/application_ref.ts @@ -18,7 +18,7 @@ import {Console} from './console'; import {Injectable} from './di/injectable'; import {InjectionToken} from './di/injection_token'; import {Injector} from './di/injector'; -import {Provider, StaticProvider} from './di/interface/provider'; +import {ImportedNgModuleProviders, Provider, StaticProvider} from './di/interface/provider'; import {EnvironmentInjector} from './di/r3_injector'; import {INJECTOR_SCOPE} from './di/scope'; import {ErrorHandler} from './error_handler'; @@ -187,7 +187,7 @@ export function runPlatformInitializers(injector: Injector): void { */ export function bootstrapApplication(config: { rootComponent: Type, - appProviders?: Provider[], + appProviders?: Array, platformProviders?: Provider[], }): Promise { const {rootComponent, appProviders, platformProviders} = config; diff --git a/packages/core/src/di/create_injector.ts b/packages/core/src/di/create_injector.ts index 295acf7f227..68f96d214a4 100644 --- a/packages/core/src/di/create_injector.ts +++ b/packages/core/src/di/create_injector.ts @@ -40,8 +40,8 @@ export function createInjectorWithoutInjectorInstances( additionalProviders: StaticProvider[]|null = null, name?: string, scopes = new Set()): R3Injector { const providers = [ - ...flatten(additionalProviders || EMPTY_ARRAY), - ...importProvidersFrom(defType), + additionalProviders || EMPTY_ARRAY, + importProvidersFrom(defType), ]; name = name || (typeof defType === 'object' ? undefined : stringify(defType)); diff --git a/packages/core/src/di/index.ts b/packages/core/src/di/index.ts index b9bcfa02cf8..34a5d50df8d 100644 --- a/packages/core/src/di/index.ts +++ b/packages/core/src/di/index.ts @@ -25,7 +25,7 @@ export {ProviderToken} from './provider_token'; export {ɵɵinject, inject, ɵɵinvalidFactoryDep} from './injector_compatibility'; export {INJECTOR} from './injector_token'; export {ReflectiveInjector} from './reflective_injector'; -export {ClassProvider, ModuleWithProviders, ClassSansProvider, ConstructorProvider, ConstructorSansProvider, ExistingProvider, ExistingSansProvider, FactoryProvider, FactorySansProvider, Provider, StaticClassProvider, StaticClassSansProvider, StaticProvider, TypeProvider, ValueProvider, ValueSansProvider} from './interface/provider'; +export {ClassProvider, ModuleWithProviders, ClassSansProvider, ImportedNgModuleProviders, ConstructorProvider, ConstructorSansProvider, ExistingProvider, ExistingSansProvider, FactoryProvider, FactorySansProvider, Provider, StaticClassProvider, StaticClassSansProvider, StaticProvider, TypeProvider, ValueProvider, ValueSansProvider} from './interface/provider'; export {ResolvedReflectiveFactory, ResolvedReflectiveProvider} from './reflective_provider'; export {ReflectiveKey} from './reflective_key'; export {InjectionToken} from './injection_token'; diff --git a/packages/core/src/di/interface/provider.ts b/packages/core/src/di/interface/provider.ts index ccb362cda43..c58311e2953 100644 --- a/packages/core/src/di/interface/provider.ts +++ b/packages/core/src/di/interface/provider.ts @@ -351,3 +351,20 @@ export interface ModuleWithProviders { ngModule: Type; providers?: Provider[]; } + +/** + * Providers that were imported from NgModules via the `importProvidersFrom` function. + * + * These providers are meant for use in an application injector (or other environment injectors) and + * should not be used in component injectors. + * + * This type cannot be directly implemented. It's returned from the `importProvidersFrom` function + * and serves to prevent the extracted NgModule providers from being used in the wrong contexts. + * + * @see `importProvidersFrom` + * + * @publicApi + */ +export interface ImportedNgModuleProviders { + ɵproviders: Provider[]; +} diff --git a/packages/core/src/di/provider_collection.ts b/packages/core/src/di/provider_collection.ts index c99c3564dd4..21039d8a03b 100644 --- a/packages/core/src/di/provider_collection.ts +++ b/packages/core/src/di/provider_collection.ts @@ -19,7 +19,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, ExistingProvider, FactoryProvider, ModuleWithProviders, Provider, StaticClassProvider, TypeProvider, ValueProvider} from './interface/provider'; +import {ClassProvider, ConstructorProvider, ExistingProvider, FactoryProvider, ImportedNgModuleProviders, ModuleWithProviders, Provider, StaticClassProvider, TypeProvider, ValueProvider} from './interface/provider'; import {INJECTOR_DEF_TYPES} from './internal_tokens'; /** @@ -34,10 +34,15 @@ export type ImportProvidersSource = * Collects providers from all NgModules and standalone components, including transitively imported * ones. * - * @returns The list of collected providers from the specified list of types. + * Providers extracted via `importProvidersFrom` are only usable in an application injector or + * another environment injector (such as a route injector). They should not be used in component + * providers. + * + * @returns The collected providers from the specified list of types. * @publicApi */ -export function importProvidersFrom(...sources: ImportProvidersSource[]): Provider[] { +export function importProvidersFrom(...sources: ImportProvidersSource[]): + ImportedNgModuleProviders { const providersOut: SingleProvider[] = []; const dedup = new Set>(); // already seen types let injectorTypesWithProviders: InjectorTypeWithProviders[]|undefined; @@ -53,7 +58,7 @@ export function importProvidersFrom(...sources: ImportProvidersSource[]): Provid if (injectorTypesWithProviders !== undefined) { processInjectorTypesWithProviders(injectorTypesWithProviders, providersOut); } - return providersOut; + return {ɵproviders: providersOut}; } /** diff --git a/packages/core/src/di/r3_injector.ts b/packages/core/src/di/r3_injector.ts index e5c0b818a0e..f8f7ec22861 100644 --- a/packages/core/src/di/r3_injector.ts +++ b/packages/core/src/di/r3_injector.ts @@ -14,7 +14,7 @@ import {Type} from '../interface/type'; import {getComponentDef} from '../render3/definition'; import {FactoryFn, getFactoryDef} from '../render3/definition_factory'; import {throwCyclicDependencyError, throwInvalidProviderError, throwMixedMultiProviderError} from '../render3/errors_di'; -import {deepForEach, flatten, newArray} from '../util/array_utils'; +import {newArray} from '../util/array_utils'; import {EMPTY_ARRAY} from '../util/empty'; import {stringify} from '../util/stringify'; @@ -27,7 +27,7 @@ import {catchInjectorError, injectArgs, NG_TEMP_TOKEN_PATH, setCurrentInjector, import {INJECTOR} from './injector_token'; import {getInheritedInjectableDef, getInjectableDef, InjectorType, ɵɵInjectableDeclaration} from './interface/defs'; import {InjectFlags} from './interface/injector'; -import {ClassProvider, ConstructorProvider, Provider, StaticClassProvider} from './interface/provider'; +import {ClassProvider, ConstructorProvider, ImportedNgModuleProviders, Provider, StaticClassProvider} from './interface/provider'; import {INJECTOR_DEF_TYPES} from './internal_tokens'; import {NullInjector} from './null_injector'; import {importProvidersFrom, isExistingProvider, isFactoryProvider, isTypeProvider, isValueProvider, SingleProvider} from './provider_collection'; @@ -121,13 +121,11 @@ export class R3Injector extends EnvironmentInjector { private injectorDefTypes: Set>; constructor( - providers: Provider[], 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. - deepForEach(providers, provider => { - this.processProvider(provider as SingleProvider); - }); + forEachSingleProvider(providers, provider => this.processProvider(provider)); // Make sure the INJECTOR token provides this injector. this.records.set(INJECTOR, makeRecord(undefined, this)); @@ -403,6 +401,10 @@ function providerToRecord(provider: SingleProvider): Record { export function providerToFactory( provider: SingleProvider, ngModuleType?: InjectorType, providers?: any[]): () => any { let factory: (() => any)|undefined = undefined; + if (ngDevMode && isImportedNgModuleProviders(provider)) { + throwInvalidProviderError(undefined, providers, provider); + } + if (isTypeProvider(provider)) { const unwrappedProvider = resolveForwardRef(provider); return getFactoryDef(unwrappedProvider) || injectableDefOrInjectorDefFactory(unwrappedProvider); @@ -453,3 +455,22 @@ function couldBeInjectableType(value: any): value is ProviderToken { return (typeof value === 'function') || (typeof value === 'object' && value instanceof InjectionToken); } + +function isImportedNgModuleProviders(provider: Provider|ImportedNgModuleProviders): + provider is ImportedNgModuleProviders { + return !!(provider as ImportedNgModuleProviders).ɵproviders; +} + +function forEachSingleProvider( + providers: Array, + fn: (provider: SingleProvider) => void): void { + for (const provider of providers) { + if (Array.isArray(provider)) { + forEachSingleProvider(provider, fn); + } else if (isImportedNgModuleProviders(provider)) { + forEachSingleProvider(provider.ɵproviders, fn); + } else { + fn(provider); + } + } +} diff --git a/packages/core/src/errors.ts b/packages/core/src/errors.ts index 064fdf0ebe5..1f131be9baa 100644 --- a/packages/core/src/errors.ts +++ b/packages/core/src/errors.ts @@ -30,6 +30,7 @@ export const enum RuntimeErrorCode { MISSING_INJECTION_CONTEXT = 203, INVALID_INJECTION_TOKEN = 204, INJECTOR_ALREADY_DESTROYED = 205, + PROVIDER_IN_WRONG_CONTEXT = 207, // Template Errors MULTIPLE_COMPONENTS_MATCH = -300, diff --git a/packages/core/src/render3/errors_di.ts b/packages/core/src/render3/errors_di.ts index 590d4b275b7..71416fc42f6 100644 --- a/packages/core/src/render3/errors_di.ts +++ b/packages/core/src/render3/errors_di.ts @@ -6,6 +6,7 @@ * found in the LICENSE file at https://angular.io/license */ +import {ImportedNgModuleProviders} from '../di/interface/provider'; import {RuntimeError, RuntimeErrorCode} from '../errors'; import {Type} from '../interface/type'; import {stringify} from '../util/stringify'; @@ -26,16 +27,19 @@ export function throwMixedMultiProviderError() { } export function throwInvalidProviderError( - ngModuleType?: Type, providers?: any[], provider?: any) { - let ngModuleDetail = ''; + ngModuleType?: Type, providers?: any[], provider?: any): never { if (ngModuleType && providers) { const providerDetail = providers.map(v => v == provider ? '?' + provider + '?' : '...'); - ngModuleDetail = - ` - only instances of Provider and Type are allowed, got: [${providerDetail.join(', ')}]`; + 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 { + throw new Error('Invalid provider'); } - - throw new Error( - `Invalid provider for the NgModule '${stringify(ngModuleType)}'` + ngModuleDetail); } diff --git a/packages/core/src/render3/features/standalone_feature.ts b/packages/core/src/render3/features/standalone_feature.ts index 2f4527d27ae..6fed11b3ddc 100644 --- a/packages/core/src/render3/features/standalone_feature.ts +++ b/packages/core/src/render3/features/standalone_feature.ts @@ -30,9 +30,9 @@ class StandaloneService implements OnDestroy { if (!this.cachedInjectors.has(componentDef)) { const providers = importProvidersFrom(componentDef.type); - const standaloneInjector = providers.length > 0 ? + const standaloneInjector = providers.ɵproviders.length > 0 ? createEnvironmentInjector( - providers, this._injector, `Standalone[${componentDef.type.name}]`) : + [providers], this._injector, `Standalone[${componentDef.type.name}]`) : null; this.cachedInjectors.set(componentDef, standaloneInjector); } diff --git a/packages/core/src/render3/ng_module_ref.ts b/packages/core/src/render3/ng_module_ref.ts index 9deab7f9f5e..1a9986fc6c2 100644 --- a/packages/core/src/render3/ng_module_ref.ts +++ b/packages/core/src/render3/ng_module_ref.ts @@ -10,7 +10,7 @@ import {createInjectorWithoutInjectorInstances} from '../di/create_injector'; import {Injector} from '../di/injector'; import {INJECTOR} from '../di/injector_token'; import {InjectFlags} from '../di/interface/injector'; -import {Provider} from '../di/interface/provider'; +import {ImportedNgModuleProviders, Provider} from '../di/interface/provider'; import {EnvironmentInjector, getNullInjector, R3Injector} from '../di/r3_injector'; import {Type} from '../interface/type'; import {ComponentFactoryResolver as viewEngine_ComponentFactoryResolver} from '../linker/component_factory_resolver'; @@ -115,7 +115,9 @@ class EnvironmentNgModuleRefAdapter extends viewEngine_NgModuleRef { new ComponentFactoryResolver(this); override readonly instance = null; - constructor(providers: Provider[], parent: EnvironmentInjector|null, source: string|null) { + constructor( + providers: Array, parent: EnvironmentInjector|null, + source: string|null) { super(); const injector = new R3Injector( [ @@ -143,7 +145,7 @@ class EnvironmentNgModuleRefAdapter extends viewEngine_NgModuleRef { * @publicApi */ export function createEnvironmentInjector( - providers: Provider[], parent: EnvironmentInjector|null = null, + providers: Array, parent: EnvironmentInjector|null = null, debugName: string|null = null): EnvironmentInjector { const adapter = new EnvironmentNgModuleRefAdapter(providers, parent, debugName); return adapter.injector; diff --git a/packages/core/test/acceptance/di_spec.ts b/packages/core/test/acceptance/di_spec.ts index d8470093680..b2bd937f278 100644 --- a/packages/core/test/acceptance/di_spec.ts +++ b/packages/core/test/acceptance/di_spec.ts @@ -7,7 +7,7 @@ */ import {CommonModule} from '@angular/common'; -import {Attribute, ChangeDetectorRef, Component, ComponentFactoryResolver, ComponentRef, createEnvironmentInjector, Directive, ElementRef, ENVIRONMENT_INITIALIZER, EventEmitter, forwardRef, Host, HostBinding, importProvidersFrom, Inject, Injectable, InjectFlags, InjectionToken, INJECTOR, Injector, Input, LOCALE_ID, ModuleWithProviders, NgModule, NgZone, Optional, Output, Pipe, PipeTransform, Provider, Self, SkipSelf, TemplateRef, Type, ViewChild, ViewContainerRef, ViewRef, ɵcreateInjector as createInjector, ɵDEFAULT_LOCALE_ID as DEFAULT_LOCALE_ID, ɵINJECTOR_SCOPE} from '@angular/core'; +import {Attribute, ChangeDetectorRef, Component, ComponentFactoryResolver, ComponentRef, createEnvironmentInjector, Directive, ElementRef, ENVIRONMENT_INITIALIZER, EventEmitter, forwardRef, Host, HostBinding, ImportedNgModuleProviders, importProvidersFrom, ImportProvidersSource, Inject, Injectable, InjectFlags, InjectionToken, INJECTOR, Injector, Input, LOCALE_ID, ModuleWithProviders, NgModule, NgZone, Optional, Output, Pipe, PipeTransform, Provider, Self, SkipSelf, TemplateRef, Type, ViewChild, ViewContainerRef, ViewRef, ɵcreateInjector as createInjector, ɵDEFAULT_LOCALE_ID as DEFAULT_LOCALE_ID, ɵINJECTOR_SCOPE} from '@angular/core'; import {ViewRef as ViewRefInternal} from '@angular/core/src/render3/view_ref'; import {TestBed} from '@angular/core/testing'; import {By} from '@angular/platform-browser'; @@ -23,6 +23,10 @@ const hasProviderWithToken = (providers: Provider[], token: InjectionToken getProvidersByToken(providers, ENVIRONMENT_INITIALIZER); +function unwrappedImportProvidersFrom(...sources: ImportProvidersSource[]): Provider[] { + return importProvidersFrom(...sources).ɵproviders; +} + describe('importProvidersFrom', () => { // Set of tokens used in various tests. const A = new InjectionToken('A'); @@ -48,7 +52,7 @@ describe('importProvidersFrom', () => { }) class MyModule { } - const providers = importProvidersFrom(MyModule); + const providers = unwrappedImportProvidersFrom(MyModule); // 4 tokens (A, B, C, D) + 2 providers for each NgModule: // - the definition type itself @@ -70,7 +74,7 @@ describe('importProvidersFrom', () => { class Module { } - const providers = importProvidersFrom({ + const providers = unwrappedImportProvidersFrom({ ngModule: Module, providers: [{provide: A, useValue: 'A'}], }); @@ -97,7 +101,7 @@ describe('importProvidersFrom', () => { } } - const providers = importProvidersFrom(ModuleB.forRoot(), ModuleB.forChild()); + const providers = unwrappedImportProvidersFrom(ModuleB.forRoot(), ModuleB.forChild()); // Expect 2 `ENVIRONMENT_INITIALIZER` providers: one for `ModuleA`, another one for `ModuleB` expect(collectEnvironmentInitializerProviders(providers).length).toBe(2); @@ -127,7 +131,7 @@ describe('importProvidersFrom', () => { } } - const providers = importProvidersFrom(ModuleA.forRoot()); + const providers = unwrappedImportProvidersFrom(ModuleA.forRoot()); // Expect 1 `ENVIRONMENT_INITIALIZER` provider (for `ModuleA`) expect(collectEnvironmentInitializerProviders(providers).length).toBe(1); @@ -161,7 +165,7 @@ describe('importProvidersFrom', () => { class ModuleB { } - const providers = importProvidersFrom(ModuleB); + const providers = unwrappedImportProvidersFrom(ModuleB); // Expect 2 `ENVIRONMENT_INITIALIZER` providers: one for `ModuleA`, another one for `ModuleB` expect(collectEnvironmentInitializerProviders(providers).length).toBe(2); @@ -198,7 +202,7 @@ describe('importProvidersFrom', () => { } } - const providers = importProvidersFrom(ModuleA.forRoot()); + const providers = unwrappedImportProvidersFrom(ModuleA.forRoot()); // Expect all tokens to be collected. expect(hasProviderWithToken(providers, A)).toBe(true); @@ -214,6 +218,26 @@ describe('importProvidersFrom', () => { // Verify that a multi-provider has both values. expect(injector.get(D)).toEqual(['Original D', 'Extra D']); }); + + it('should not be allowed in component providers', () => { + @NgModule({}) + class Module { + } + + expect(() => { + @Component({ + selector: 'test-cmp', + template: '', + // The double array here is necessary to escape the compile-time error, via Provider's + // `any[]` option. + providers: [[importProvidersFrom(Module)]], + }) + class Cmp { + } + + TestBed.createComponent(Cmp); + }).toThrowError(/NG0207/); + }); }); describe('di', () => { diff --git a/packages/core/test/acceptance/env_injector_standalone_spec.ts b/packages/core/test/acceptance/env_injector_standalone_spec.ts index 937ba941ef6..dfa4248023d 100644 --- a/packages/core/test/acceptance/env_injector_standalone_spec.ts +++ b/packages/core/test/acceptance/env_injector_standalone_spec.ts @@ -20,7 +20,7 @@ describe('environement injector and standalone components', () => { class StandaloneComponent { } - const envInjector = createEnvironmentInjector(importProvidersFrom(StandaloneComponent)); + const envInjector = createEnvironmentInjector([importProvidersFrom(StandaloneComponent)]); expect(envInjector.get(ModuleService)).toBeInstanceOf(ModuleService); }); @@ -39,7 +39,7 @@ describe('environement injector and standalone components', () => { class AppModule { } - const envInjector = createEnvironmentInjector(importProvidersFrom(AppModule)); + const envInjector = createEnvironmentInjector([importProvidersFrom(AppModule)]); expect(envInjector.get(ModuleService)).toBeInstanceOf(ModuleService); }); @@ -65,7 +65,7 @@ describe('environement injector and standalone components', () => { class AppModule { } - const envInjector = createEnvironmentInjector(importProvidersFrom(AppModule)); + const envInjector = createEnvironmentInjector([importProvidersFrom(AppModule)]); const services = envInjector.get(ModuleService) as ModuleService[]; expect(services.length).toBe(1); diff --git a/packages/core/test/bundling/animations/bundle.golden_symbols.json b/packages/core/test/bundling/animations/bundle.golden_symbols.json index ca1e0f0b050..631b50b3050 100644 --- a/packages/core/test/bundling/animations/bundle.golden_symbols.json +++ b/packages/core/test/bundling/animations/bundle.golden_symbols.json @@ -767,15 +767,15 @@ { "name": "findAttrIndexInNode" }, - { - "name": "flatten" - }, { "name": "flattenStyles" }, { "name": "flattenUnsubscriptionErrors" }, + { + "name": "forEachSingleProvider" + }, { "name": "forwardRef" }, @@ -1007,6 +1007,9 @@ { "name": "isFunction" }, + { + "name": "isImportedNgModuleProviders" + }, { "name": "isInCheckNoChangesMode" }, 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 d52696c6f98..e191b4ff257 100644 --- a/packages/core/test/bundling/forms_reactive/bundle.golden_symbols.json +++ b/packages/core/test/bundling/forms_reactive/bundle.golden_symbols.json @@ -791,15 +791,15 @@ { "name": "findStylingValue" }, - { - "name": "flatten" - }, { "name": "flattenStyles" }, { "name": "flattenUnsubscriptionErrors" }, + { + "name": "forEachSingleProvider" + }, { "name": "forkJoinInternal" }, @@ -1109,6 +1109,9 @@ { "name": "isFunction" }, + { + "name": "isImportedNgModuleProviders" + }, { "name": "isInCheckNoChangesMode" }, 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 5a2d70d927e..938fad62f62 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 @@ -764,15 +764,15 @@ { "name": "findStylingValue" }, - { - "name": "flatten" - }, { "name": "flattenStyles" }, { "name": "flattenUnsubscriptionErrors" }, + { + "name": "forEachSingleProvider" + }, { "name": "forkJoinInternal" }, @@ -1073,6 +1073,9 @@ { "name": "isFunction" }, + { + "name": "isImportedNgModuleProviders" + }, { "name": "isInCheckNoChangesMode" }, diff --git a/packages/core/test/bundling/injection/bundle.golden_symbols.json b/packages/core/test/bundling/injection/bundle.golden_symbols.json index aa0ad446e31..3dc42c82503 100644 --- a/packages/core/test/bundling/injection/bundle.golden_symbols.json +++ b/packages/core/test/bundling/injection/bundle.golden_symbols.json @@ -84,7 +84,7 @@ "name": "deepForEach" }, { - "name": "flatten" + "name": "forEachSingleProvider" }, { "name": "forwardRef" @@ -122,6 +122,9 @@ { "name": "injectableDefOrInjectorDefFactory" }, + { + "name": "isImportedNgModuleProviders" + }, { "name": "isTypeProvider" }, diff --git a/packages/core/test/bundling/router/bundle.golden_symbols.json b/packages/core/test/bundling/router/bundle.golden_symbols.json index 431966b1aff..620486094b7 100644 --- a/packages/core/test/bundling/router/bundle.golden_symbols.json +++ b/packages/core/test/bundling/router/bundle.golden_symbols.json @@ -1118,6 +1118,9 @@ { "name": "forEach" }, + { + "name": "forEachSingleProvider" + }, { "name": "forwardRef" }, @@ -1448,6 +1451,9 @@ { "name": "isImmediateMatch" }, + { + "name": "isImportedNgModuleProviders" + }, { "name": "isInCheckNoChangesMode" }, 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 0d1fe2792fa..6df6ee3cebf 100644 --- a/packages/core/test/bundling/standalone_bootstrap/bundle.golden_symbols.json +++ b/packages/core/test/bundling/standalone_bootstrap/bundle.golden_symbols.json @@ -485,15 +485,15 @@ { "name": "extractDirectiveDef" }, - { - "name": "flatten" - }, { "name": "flattenStyles" }, { "name": "flattenUnsubscriptionErrors" }, + { + "name": "forEachSingleProvider" + }, { "name": "forwardRef" }, @@ -650,6 +650,9 @@ { "name": "isFunction" }, + { + "name": "isImportedNgModuleProviders" + }, { "name": "isInCheckNoChangesMode" }, diff --git a/packages/platform-browser/src/browser.ts b/packages/platform-browser/src/browser.ts index 59639a7b851..795121062d5 100644 --- a/packages/platform-browser/src/browser.ts +++ b/packages/platform-browser/src/browser.ts @@ -7,7 +7,7 @@ */ import {CommonModule, DOCUMENT, XhrFactory, ɵPLATFORM_BROWSER_ID as PLATFORM_BROWSER_ID} from '@angular/common'; -import {APP_ID, ApplicationModule, ApplicationRef, createPlatformFactory, ErrorHandler, Inject, ModuleWithProviders, NgModule, NgZone, Optional, PLATFORM_ID, PLATFORM_INITIALIZER, platformCore, PlatformRef, Provider, RendererFactory2, SkipSelf, StaticProvider, Testability, Type, ɵbootstrapApplication as _bootstrapApplication, ɵINJECTOR_SCOPE as INJECTOR_SCOPE, ɵsetDocument} from '@angular/core'; +import {APP_ID, ApplicationModule, ApplicationRef, createPlatformFactory, ErrorHandler, ImportedNgModuleProviders, Inject, ModuleWithProviders, NgModule, NgZone, Optional, PLATFORM_ID, PLATFORM_INITIALIZER, platformCore, PlatformRef, Provider, RendererFactory2, SkipSelf, StaticProvider, Testability, Type, ɵbootstrapApplication as _bootstrapApplication, ɵINJECTOR_SCOPE as INJECTOR_SCOPE, ɵsetDocument} from '@angular/core'; import {BrowserDomAdapter} from './browser/browser_adapter'; import {SERVER_TRANSITION_PROVIDERS, TRANSITION_ID} from './browser/server-transition'; @@ -28,7 +28,7 @@ export interface ApplicationConfig { /** * List of providers that should be available to the root component and all its children. */ - providers: Provider[]; + providers: Array; } /** diff --git a/packages/platform-server/src/utils.ts b/packages/platform-server/src/utils.ts index 49101d3f4d4..e7d01020a2c 100644 --- a/packages/platform-server/src/utils.ts +++ b/packages/platform-server/src/utils.ts @@ -6,7 +6,7 @@ * found in the LICENSE file at https://angular.io/license */ -import {ApplicationRef, importProvidersFrom, Injector, NgModuleFactory, NgModuleRef, PlatformRef, Provider, StaticProvider, Type, ɵbootstrapApplication as bootstrapApplication, ɵisPromise} from '@angular/core'; +import {ApplicationRef, ImportedNgModuleProviders, importProvidersFrom, Injector, NgModuleFactory, NgModuleRef, PlatformRef, Provider, StaticProvider, Type, ɵbootstrapApplication as bootstrapApplication, ɵisPromise} from '@angular/core'; import {BrowserModule, ɵTRANSITION_ID} from '@angular/platform-browser'; import {first} from 'rxjs/operators'; @@ -141,14 +141,14 @@ export function renderApplication(rootComponent: Type, options: { appId: string, document?: string, url?: string, - providers?: Provider[], + providers?: Array, platformProviders?: Provider[], }): Promise { const {document, url, platformProviders, appId} = options; const platform = _getPlatform(platformDynamicServer, {document, url, platformProviders}); const appProviders = [ - ...importProvidersFrom(BrowserModule.withServerTransition({appId})), - ...importProvidersFrom(ServerModule), + importProvidersFrom(BrowserModule.withServerTransition({appId})), + importProvidersFrom(ServerModule), ...(options.providers ?? []), ]; return _render(platform, bootstrapApplication({rootComponent, appProviders})); diff --git a/packages/router/src/models.ts b/packages/router/src/models.ts index 12530b9af08..3415da7f6eb 100644 --- a/packages/router/src/models.ts +++ b/packages/router/src/models.ts @@ -6,7 +6,7 @@ * found in the LICENSE file at https://angular.io/license */ -import {EnvironmentInjector, NgModuleFactory, Provider, Type} from '@angular/core'; +import {EnvironmentInjector, ImportedNgModuleProviders, NgModuleFactory, Provider, Type} from '@angular/core'; import {Observable} from 'rxjs'; import {ActivatedRouteSnapshot, RouterStateSnapshot} from './router_state'; @@ -508,7 +508,7 @@ export interface Route { * route also has a `loadChildren` function which returns an `NgModuleRef`, this injector will be * used as the parent of the lazy loaded module. */ - providers?: Provider[]; + providers?: Array; /** * Injector created from the static route providers