refactor(core): support EnvironmentProviders types internally (#47669)

This commit modifies `R3Injector` and other code in Angular that deals with
providers, to handle `EnvironmentProviders` objects as well as normal
`Provider` types. There is no user-visible impact to this change, but it
prepares the core of the DI system for the introduction of
`EnvironmentProviders` as a public feature.

PR Close #47669
This commit is contained in:
Alex Rickabaugh 2022-10-05 12:13:06 -07:00 committed by Jessica Janiuk
parent 3133351f25
commit c5a1b90b25
13 changed files with 143 additions and 36 deletions

View file

@ -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<unknown>[], providersOut: Provider[]): void {
for (let i = 0; i < typesWithProviders.length; i++) {
const {ngModule, providers} = typesWithProviders[i];
deepForEach(providers!, provider => {
deepForEachProvider(providers! as Array<Provider|InternalEnvironmentProviders>, 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<SingleProvider|InternalEnvironmentProviders>;
if (defProviders != null && !isDuplicate) {
const injectorType = container as InjectorType<any>;
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<unknown>): void {
provider: SingleProvider, providers: Array<SingleProvider|InternalEnvironmentProviders>,
containerType: Type<unknown>): void {
if (isTypeProvider(provider) || isValueProvider(provider) || isFactoryProvider(provider) ||
isExistingProvider(provider)) {
return;
@ -294,6 +295,21 @@ function validateProvider(
}
}
function deepForEachProvider(
providers: Array<Provider|InternalEnvironmentProviders>,
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<ValueProvider>({provide: String, useValue: getClosureSafeProperty});

View file

@ -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<Type<unknown>>;
constructor(
providers: Array<Provider|ImportedNgModuleProviders>, readonly parent: Injector,
readonly source: string|null, readonly scopes: Set<InjectorScope>) {
providers: Array<Provider|ImportedNgModuleProviders|EnvironmentProviders>,
readonly parent: Injector, readonly source: string|null,
readonly scopes: Set<InjectorScope>) {
super();
// Start off by creating Records for every provider.
forEachSingleProvider(providers, provider => this.processProvider(provider));
forEachSingleProvider(
providers as Array<Provider|ImportedNgModuleProviders|InternalEnvironmentProviders>,
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<any> {
export function providerToFactory(
provider: SingleProvider, ngModuleType?: InjectorType<any>, 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<any> {
function isImportedNgModuleProviders(provider: Provider|ImportedNgModuleProviders):
provider is ImportedNgModuleProviders {
return !!(provider as ImportedNgModuleProviders).ɵproviders;
return provider && !!(provider as ImportedNgModuleProviders).ɵproviders;
}
function forEachSingleProvider(
providers: Array<Provider|ImportedNgModuleProviders>,
providers: Array<Provider|ImportedNgModuleProviders|InternalEnvironmentProviders>,
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);
}
}
}

View file

@ -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');
}

View file

@ -710,6 +710,9 @@
{
"name": "deepForEach"
},
{
"name": "deepForEachProvider"
},
{
"name": "detachMovedView"
},
@ -1001,6 +1004,9 @@
{
"name": "isElementNode"
},
{
"name": "isEnvironmentProviders"
},
{
"name": "isFunction"
},

View file

@ -497,6 +497,9 @@
{
"name": "deepForEach"
},
{
"name": "deepForEachProvider"
},
{
"name": "detachMovedView"
},
@ -749,6 +752,9 @@
{
"name": "isCurrentTNodeParent"
},
{
"name": "isEnvironmentProviders"
},
{
"name": "isFunction"
},

View file

@ -722,6 +722,9 @@
{
"name": "deepForEach"
},
{
"name": "deepForEachProvider"
},
{
"name": "defaultIterableDiffersFactory"
},
@ -1097,6 +1100,9 @@
{
"name": "isEmptyInputValue"
},
{
"name": "isEnvironmentProviders"
},
{
"name": "isFormControlState"
},

View file

@ -692,6 +692,9 @@
{
"name": "deepForEach"
},
{
"name": "deepForEachProvider"
},
{
"name": "defaultIterableDiffersFactory"
},
@ -1055,6 +1058,9 @@
{
"name": "isDirectiveHost"
},
{
"name": "isEnvironmentProviders"
},
{
"name": "isFormControlState"
},

View file

@ -359,6 +359,9 @@
{
"name": "deepForEach"
},
{
"name": "deepForEachProvider"
},
{
"name": "detachMovedView"
},
@ -557,6 +560,9 @@
{
"name": "isComponentDef"
},
{
"name": "isEnvironmentProviders"
},
{
"name": "isFunction"
},

View file

@ -83,6 +83,9 @@
{
"name": "deepForEach"
},
{
"name": "deepForEachProvider"
},
{
"name": "forEachSingleProvider"
},
@ -122,6 +125,9 @@
{
"name": "internalImportProvidersFrom"
},
{
"name": "isEnvironmentProviders"
},
{
"name": "isImportedNgModuleProviders"
},

View file

@ -884,6 +884,9 @@
{
"name": "deepForEach"
},
{
"name": "deepForEachProvider"
},
{
"name": "defaultErrorFactory"
},
@ -1340,6 +1343,9 @@
{
"name": "isEmptyError"
},
{
"name": "isEnvironmentProviders"
},
{
"name": "isFunction"
},

View file

@ -428,6 +428,9 @@
{
"name": "deepForEach"
},
{
"name": "deepForEachProvider"
},
{
"name": "detachMovedView"
},
@ -641,6 +644,9 @@
{
"name": "isComponentDef"
},
{
"name": "isEnvironmentProviders"
},
{
"name": "isFunction"
},

View file

@ -602,6 +602,9 @@
{
"name": "deepForEach"
},
{
"name": "deepForEachProvider"
},
{
"name": "defaultIterableDiffersFactory"
},
@ -926,6 +929,9 @@
{
"name": "isDirectiveHost"
},
{
"name": "isEnvironmentProviders"
},
{
"name": "isFunction"
},

View file

@ -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<Provider|InternalEnvironmentProviders> = [
...injectorDef.providers,
...(this.providerOverridesByModule.get(type as InjectorType<any>) || [])
];
@ -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<Provider|InternalEnvironmentProviders>);
}
}
}
@ -797,21 +798,23 @@ export class TestBedCompiler {
return this.providerOverridesByToken.get(token) || null;
}
private getProviderOverrides(providers?: Provider[]): Provider[] {
private getProviderOverrides(providers?: Array<Provider|InternalEnvironmentProviders>):
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|InternalEnvironmentProviders>):
Provider[] {
if (!providers || !providers.length || this.providerOverridesByToken.size === 0) return [];
const flattenedProviders = flatten<Provider[]>(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<Provider|InternalEnvironmentProviders>): boolean {
return this.getProviderOverrides(providers).length > 0;
}
@ -887,18 +890,42 @@ function maybeUnwrapFn<T>(maybeFn: (() => T)|T): T {
return maybeFn instanceof Function ? maybeFn() : maybeFn;
}
function flatten<T>(values: any[], mapFn?: (value: T) => any): T[] {
function flatten<T>(values: any[]): T[] {
const out: T[] = [];
values.forEach(value => {
if (Array.isArray(value)) {
out.push(...flatten<T>(value, mapFn));
out.push(...flatten<T>(value));
} else {
out.push(mapFn ? mapFn(value) : value);
out.push(value);
}
});
return out;
}
function identityFn<T>(value: T): T {
return value;
}
function flattenProviders<T>(
providers: Array<Provider|InternalEnvironmentProviders>, mapFn: (provider: Provider) => T): T[];
function flattenProviders(providers: Array<Provider|InternalEnvironmentProviders>): Provider[];
function flattenProviders(
providers: Array<Provider|InternalEnvironmentProviders>,
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];
}