Skip to content

Commit

Permalink
feat(core): introduce EnvironmentProviders wrapper type (#47669)
Browse files Browse the repository at this point in the history
This commit introduces a new type `EnvironmentProviders` which can be used
in contexts where Angular accepted `Provider`s destined for
`EnvironmentInjector`s. This includes contexts such as `@NgModule.providers`
and `Route.providers`.

The new type is useful for preventing such providers from accidentally
ending up in `@Component.providers`. It can be used as the return type of
provider functions (such as `provideRouter`) to enforce this safety.

Because `Provider` allows `any[]` nested arrays, the compile-time safety
provided by `EnvironmentProviders` is easily circumvented. However, the
runtime shape of `EnvironmentProviders` is not compatible with component
injectors and will result in a runtime error if it leaks through (NG0207).

A new function `makeEnvironmentProviders` is used to construct this new type
from an array of providers.

The existing `importProvidersFrom` operation previously returned a very
similar type `ImportedNgModuleProviders` which had the same goal. This
machinery is switched over to use the new `EnvironmentProviders` interface
instead (in fact, `ImportedNgModuleProviders` is now just an alias to
`EnvironmentProviders`).

PR Close #47669
  • Loading branch information
alxhub authored and thePunderWoman committed Oct 7, 2022
1 parent c5a1b90 commit 7de1469
Show file tree
Hide file tree
Showing 25 changed files with 145 additions and 84 deletions.
23 changes: 14 additions & 9 deletions goldens/public-api/core/index.md
Expand Up @@ -324,7 +324,7 @@ export function createComponent<C>(component: Type<C>, options: {
}): ComponentRef<C>;

// @public
export function createEnvironmentInjector(providers: Array<Provider | ImportedNgModuleProviders>, parent: EnvironmentInjector, debugName?: string | null): EnvironmentInjector;
export function createEnvironmentInjector(providers: Array<Provider | EnvironmentProviders>, parent: EnvironmentInjector, debugName?: string | null): EnvironmentInjector;

// @public
export function createNgModule<T>(ngModule: Type<T>, parentInjector?: Injector): NgModuleRef<T>;
Expand Down Expand Up @@ -509,6 +509,11 @@ export abstract class EnvironmentInjector implements Injector {
abstract runInContext<ReturnT>(fn: () => ReturnT): ReturnT;
}

// @public
export type EnvironmentProviders = {
ɵbrand: 'EnvironmentProviders';
};

// @public
export class ErrorHandler {
// (undocumented)
Expand Down Expand Up @@ -627,14 +632,11 @@ export interface HostListenerDecorator {
new (eventName: string, args?: string[]): any;
}

// @public
export interface ImportedNgModuleProviders {
// (undocumented)
ɵproviders: Provider[];
}
// @public @deprecated
export type ImportedNgModuleProviders = EnvironmentProviders;

// @public
export function importProvidersFrom(...sources: ImportProvidersSource[]): ImportedNgModuleProviders;
export function importProvidersFrom(...sources: ImportProvidersSource[]): EnvironmentProviders;

// @public
export type ImportProvidersSource = Type<unknown> | ModuleWithProviders<unknown> | Array<ImportProvidersSource>;
Expand Down Expand Up @@ -878,6 +880,9 @@ export class KeyValueDiffers {
// @public
export const LOCALE_ID: InjectionToken<string>;

// @public
export function makeEnvironmentProviders(providers: Provider[]): EnvironmentProviders;

// @public
export enum MissingTranslationStrategy {
// (undocumented)
Expand All @@ -902,7 +907,7 @@ export interface ModuleWithProviders<T> {
// (undocumented)
ngModule: Type<T>;
// (undocumented)
providers?: Provider[];
providers?: Array<Provider | EnvironmentProviders>;
}

// @public
Expand All @@ -918,7 +923,7 @@ export interface NgModule {
id?: string;
imports?: Array<Type<any> | ModuleWithProviders<{}> | any[]>;
jit?: true;
providers?: Provider[];
providers?: Array<Provider | EnvironmentProviders>;
schemas?: Array<SchemaMetadata | any[]>;
}

Expand Down
4 changes: 2 additions & 2 deletions goldens/public-api/platform-browser/index.md
Expand Up @@ -8,9 +8,9 @@ import { ApplicationRef } from '@angular/core';
import { ComponentRef } from '@angular/core';
import { DebugElement } from '@angular/core';
import { DebugNode } from '@angular/core';
import { EnvironmentProviders } 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';
Expand All @@ -25,7 +25,7 @@ import { Version } from '@angular/core';

// @public
export interface ApplicationConfig {
providers: Array<Provider | ImportedNgModuleProviders>;
providers: Array<Provider | EnvironmentProviders>;
}

// @public
Expand Down
4 changes: 2 additions & 2 deletions goldens/public-api/platform-server/index.md
Expand Up @@ -4,11 +4,11 @@
```ts

import { EnvironmentProviders } from '@angular/core';
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';
Expand Down Expand Up @@ -53,7 +53,7 @@ export function renderApplication<T>(rootComponent: Type<T>, options: {
appId: string;
document?: string | Document;
url?: string;
providers?: Array<Provider | ImportedNgModuleProviders>;
providers?: Array<Provider | EnvironmentProviders>;
platformProviders?: Provider[];
}): Promise<string>;

Expand Down
4 changes: 2 additions & 2 deletions goldens/public-api/router/index.md
Expand Up @@ -11,9 +11,9 @@ import { ComponentFactoryResolver } from '@angular/core';
import { ComponentRef } from '@angular/core';
import { ElementRef } from '@angular/core';
import { EnvironmentInjector } from '@angular/core';
import { EnvironmentProviders } 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';
Expand Down Expand Up @@ -596,7 +596,7 @@ export interface Route {
outlet?: string;
path?: string;
pathMatch?: 'prefix' | 'full';
providers?: Array<Provider | ImportedNgModuleProviders>;
providers?: Array<Provider | EnvironmentProviders>;
redirectTo?: string;
resolve?: ResolveData;
runGuardsAndResolvers?: RunGuardsAndResolvers;
Expand Down
4 changes: 2 additions & 2 deletions packages/core/src/application_ref.ts
Expand Up @@ -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 {ImportedNgModuleProviders, Provider, StaticProvider} from './di/interface/provider';
import {EnvironmentProviders, Provider, StaticProvider} from './di/interface/provider';
import {EnvironmentInjector} from './di/r3_injector';
import {INJECTOR_SCOPE} from './di/scope';
import {ErrorHandler} from './error_handler';
Expand Down Expand Up @@ -189,7 +189,7 @@ export function runPlatformInitializers(injector: Injector): void {
*/
export function internalCreateApplication(config: {
rootComponent?: Type<unknown>,
appProviders?: Array<Provider|ImportedNgModuleProviders>,
appProviders?: Array<Provider|EnvironmentProviders>,
platformProviders?: Provider[],
}): Promise<ApplicationRef> {
const {rootComponent, appProviders, platformProviders} = config;
Expand Down
4 changes: 2 additions & 2 deletions packages/core/src/di/index.ts
Expand Up @@ -19,14 +19,14 @@ export {forwardRef, resolveForwardRef, ForwardRefFn} from './forward_ref';
export {Injectable, InjectableDecorator, InjectableProvider} from './injectable';
export {Injector} from './injector';
export {EnvironmentInjector} from './r3_injector';
export {importProvidersFrom, ImportProvidersSource} from './provider_collection';
export {importProvidersFrom, ImportProvidersSource, makeEnvironmentProviders} from './provider_collection';
export {ENVIRONMENT_INITIALIZER} from './initializer_token';
export {ProviderToken} from './provider_token';
export {ɵɵinject, inject, ɵɵinvalidFactoryDep} from './injector_compatibility';
export {InjectOptions} from './interface/injector';
export {INJECTOR} from './injector_token';
export {ReflectiveInjector} from './reflective_injector';
export {ClassProvider, ModuleWithProviders, ClassSansProvider, ImportedNgModuleProviders, ConstructorProvider, ConstructorSansProvider, ExistingProvider, ExistingSansProvider, FactoryProvider, FactorySansProvider, Provider, StaticClassProvider, StaticClassSansProvider, StaticProvider, TypeProvider, ValueProvider, ValueSansProvider} from './interface/provider';
export {ClassProvider, ModuleWithProviders, ClassSansProvider, ImportedNgModuleProviders, ConstructorProvider, EnvironmentProviders, 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';
6 changes: 3 additions & 3 deletions packages/core/src/di/interface/defs.ts
Expand Up @@ -9,7 +9,7 @@
import {Type} from '../../interface/type';
import {getClosureSafeProperty} from '../../util/property';

import {ClassProvider, ConstructorProvider, ExistingProvider, FactoryProvider, StaticClassProvider, ValueProvider} from './provider';
import {ClassProvider, ConstructorProvider, EnvironmentProviders, ExistingProvider, FactoryProvider, StaticClassProvider, ValueProvider} from './provider';



Expand Down Expand Up @@ -73,7 +73,7 @@ export interface ɵɵInjectorDef<T> {
// TODO(alxhub): Narrow down the type here once decorators properly change the return type of the
// class they are decorating (to add the ɵprov property for example).
providers: (Type<any>|ValueProvider|ExistingProvider|FactoryProvider|ConstructorProvider|
StaticClassProvider|ClassProvider|any[])[];
StaticClassProvider|ClassProvider|EnvironmentProviders|any[])[];

imports: (InjectorType<any>|InjectorTypeWithProviders<any>)[];
}
Expand Down Expand Up @@ -119,7 +119,7 @@ export interface InjectorType<T> extends Type<T> {
export interface InjectorTypeWithProviders<T> {
ngModule: InjectorType<T>;
providers?: (Type<any>|ValueProvider|ExistingProvider|FactoryProvider|ConstructorProvider|
StaticClassProvider|ClassProvider|any[])[];
StaticClassProvider|ClassProvider|EnvironmentProviders|any[])[];
}


Expand Down
8 changes: 3 additions & 5 deletions packages/core/src/di/interface/provider.ts
Expand Up @@ -384,7 +384,7 @@ export type ProcessProvidersFunction = (providers: Provider[]) => Provider[];
*/
export interface ModuleWithProviders<T> {
ngModule: Type<T>;
providers?: Provider[];
providers?: Array<Provider|EnvironmentProviders>;
}

/**
Expand All @@ -399,8 +399,6 @@ export interface ModuleWithProviders<T> {
* @see `importProvidersFrom`
*
* @publicApi
* @developerPreview
* @deprecated replaced by `EnvironmentProviders`
*/
export interface ImportedNgModuleProviders {
ɵproviders: Provider[];
}
export type ImportedNgModuleProviders = EnvironmentProviders;
8 changes: 5 additions & 3 deletions packages/core/src/di/provider_collection.ts
Expand Up @@ -84,9 +84,11 @@ export type ImportProvidersSource =
* @publicApi
* @developerPreview
*/
export function importProvidersFrom(...sources: ImportProvidersSource[]):
ImportedNgModuleProviders {
return {ɵproviders: internalImportProvidersFrom(true, sources)};
export function importProvidersFrom(...sources: ImportProvidersSource[]): EnvironmentProviders {
return {
ɵproviders: internalImportProvidersFrom(true, sources),
ɵfromNgModule: true,
} as InternalEnvironmentProviders;
}

export function internalImportProvidersFrom(
Expand Down
21 changes: 7 additions & 14 deletions packages/core/src/di/r3_injector.ts
Expand Up @@ -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, EnvironmentProviders, ImportedNgModuleProviders, InternalEnvironmentProviders, isEnvironmentProviders, Provider, StaticClassProvider} from './interface/provider';
import {ClassProvider, ConstructorProvider, EnvironmentProviders, 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';
Expand Down Expand Up @@ -157,13 +157,12 @@ export class R3Injector extends EnvironmentInjector {
private injectorDefTypes: Set<Type<unknown>>;

constructor(
providers: Array<Provider|ImportedNgModuleProviders|EnvironmentProviders>,
readonly parent: Injector, readonly source: string|null,
readonly scopes: Set<InjectorScope>) {
providers: Array<Provider|EnvironmentProviders>, readonly parent: Injector,
readonly source: string|null, readonly scopes: Set<InjectorScope>) {
super();
// Start off by creating Records for every provider.
forEachSingleProvider(
providers as Array<Provider|ImportedNgModuleProviders|InternalEnvironmentProviders>,
providers as Array<Provider|InternalEnvironmentProviders>,
provider => this.processProvider(provider));

// Make sure the INJECTOR token provides this injector.
Expand Down Expand Up @@ -463,7 +462,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) || isEnvironmentProviders(provider))) {
if (ngDevMode && isEnvironmentProviders(provider)) {
throwInvalidProviderError(undefined, providers, provider);
}

Expand Down Expand Up @@ -518,19 +517,13 @@ function couldBeInjectableType(value: any): value is ProviderToken<any> {
(typeof value === 'object' && value instanceof InjectionToken);
}

function isImportedNgModuleProviders(provider: Provider|ImportedNgModuleProviders):
provider is ImportedNgModuleProviders {
return provider && !!(provider as ImportedNgModuleProviders).ɵproviders;
}

function forEachSingleProvider(
providers: Array<Provider|ImportedNgModuleProviders|InternalEnvironmentProviders>,
providers: Array<Provider|InternalEnvironmentProviders>,
fn: (provider: SingleProvider) => void): void {
for (const provider of providers) {
if (Array.isArray(provider)) {
forEachSingleProvider(provider, fn);
} else if (
provider && (isImportedNgModuleProviders(provider) || isEnvironmentProviders(provider))) {
} else if (provider && isEnvironmentProviders(provider)) {
forEachSingleProvider(provider.ɵproviders, fn);
} else {
fn(provider as SingleProvider);
Expand Down
4 changes: 2 additions & 2 deletions packages/core/src/metadata/ng_module.ts
Expand Up @@ -6,7 +6,7 @@
* found in the LICENSE file at https://angular.io/license
*/

import {ModuleWithProviders, Provider} from '../di/interface/provider';
import {EnvironmentProviders, ModuleWithProviders, Provider} from '../di/interface/provider';
import {Type} from '../interface/type';
import {SchemaMetadata} from '../metadata/schema';
import {compileNgModule} from '../render3/jit/module';
Expand Down Expand Up @@ -79,7 +79,7 @@ export interface NgModule {
* }
* ```
*/
providers?: Provider[];
providers?: Array<Provider|EnvironmentProviders>;

/**
* The set of components, directives, and pipes ([declarables](guide/glossary#declarable))
Expand Down
6 changes: 3 additions & 3 deletions packages/core/src/render3/ng_module_ref.ts
Expand Up @@ -8,7 +8,7 @@

import {createInjectorWithoutInjectorInstances} from '../di/create_injector';
import {Injector} from '../di/injector';
import {ImportedNgModuleProviders, Provider} from '../di/interface/provider';
import {EnvironmentProviders, 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';
Expand Down Expand Up @@ -119,7 +119,7 @@ class EnvironmentNgModuleRefAdapter extends viewEngine_NgModuleRef<null> {
override readonly instance = null;

constructor(
providers: Array<Provider|ImportedNgModuleProviders>, parent: EnvironmentInjector|null,
providers: Array<Provider|EnvironmentProviders>, parent: EnvironmentInjector|null,
source: string|null) {
super();
const injector = new R3Injector(
Expand Down Expand Up @@ -157,7 +157,7 @@ class EnvironmentNgModuleRefAdapter extends viewEngine_NgModuleRef<null> {
* @developerPreview
*/
export function createEnvironmentInjector(
providers: Array<Provider|ImportedNgModuleProviders>, parent: EnvironmentInjector,
providers: Array<Provider|EnvironmentProviders>, parent: EnvironmentInjector,
debugName: string|null = null): EnvironmentInjector {
const adapter = new EnvironmentNgModuleRefAdapter(providers, parent, debugName);
return adapter.injector;
Expand Down

0 comments on commit 7de1469

Please sign in to comment.