Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

feat(core): introduce EnvironmentProviders wrapper type #47669

Closed
wants to merge 4 commits into from
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Jump to
Jump to file
Failed to load files.
Diff view
Diff view
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 @@ -504,6 +504,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 @@ -622,14 +627,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 @@ -873,6 +875,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 @@ -897,7 +902,7 @@ export interface ModuleWithProviders<T> {
// (undocumented)
ngModule: Type<T>;
// (undocumented)
providers?: Provider[];
providers?: Array<Provider | EnvironmentProviders>;
}

// @public
Expand All @@ -913,7 +918,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
6 changes: 3 additions & 3 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 @@ -524,7 +524,7 @@ export abstract class PreloadingStrategy {
export const PRIMARY_OUTLET = "primary";

// @public
export function provideRouter(routes: Routes, ...features: RouterFeatures[]): Provider[];
export function provideRouter(routes: Routes, ...features: RouterFeatures[]): EnvironmentProviders;

// @public
export function provideRoutes(routes: Routes): Provider[];
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
1 change: 1 addition & 0 deletions packages/core/src/core_private_export.ts
Expand Up @@ -14,6 +14,7 @@ export {Console as ɵConsole} from './console';
export {getDebugNodeR2 as ɵgetDebugNodeR2} from './debug/debug_node';
export {convertToBitFlags as ɵconvertToBitFlags, setCurrentInjector as ɵsetCurrentInjector} from './di/injector_compatibility';
export {getInjectableDef as ɵgetInjectableDef, ɵɵInjectableDeclaration, ɵɵInjectorDef} from './di/interface/defs';
export {InternalEnvironmentProviders as ɵInternalEnvironmentProviders, isEnvironmentProviders as ɵisEnvironmentProviders} from './di/interface/provider';
export {INJECTOR_SCOPE as ɵINJECTOR_SCOPE} from './di/scope';
export {formatRuntimeError as ɵformatRuntimeError, RuntimeError as ɵRuntimeError} from './errors';
export {CurrencyIndex as ɵCurrencyIndex, ExtraLocaleDataIndex as ɵExtraLocaleDataIndex, findLocaleData as ɵfindLocaleData, getLocaleCurrencyCode as ɵgetLocaleCurrencyCode, getLocalePluralCase as ɵgetLocalePluralCase, LocaleDataIndex as ɵLocaleDataIndex, registerLocaleData as ɵregisterLocaleData, unregisterAllLocaleData as ɵunregisterLocaleData} from './i18n/locale_data_api';
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
43 changes: 38 additions & 5 deletions packages/core/src/di/interface/provider.ts
Expand Up @@ -332,6 +332,41 @@ export interface ClassProvider extends ClassSansProvider {
export type Provider = TypeProvider|ValueProvider|ClassProvider|ConstructorProvider|
ExistingProvider|FactoryProvider|any[];

/**
* Encapsulated `Provider`s that are only accepted during creation of an `EnvironmentInjector` (e.g.
* in an `NgModule`).
*
* Using this wrapper type prevents providers which are only designed to work in
* application/environment injectors from being accidentally included in
* `@Component.providers` and ending up in a component injector.
*
* This wrapper type prevents access to the `Provider`s inside.
*
* @see `makeEnvironmentProviders`
* @see `importProvidersFrom`
*
* @publicApi
*/
export type EnvironmentProviders = {
ɵbrand: 'EnvironmentProviders';
};

export interface InternalEnvironmentProviders extends EnvironmentProviders {
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

nit: may be add a quick comment on why we need this type (I think it's covered in the ɵfromNgModule field description, may be just add a version of it as a comment about this function)?

ɵproviders: Provider[];

/**
* If present, indicates that the `EnvironmentProviders` were derived from NgModule providers.
*
* This is used to produce clearer error messages.
*/
ɵfromNgModule?: true;
}

export function isEnvironmentProviders(value: Provider|InternalEnvironmentProviders):
value is InternalEnvironmentProviders {
return value && !!(value as InternalEnvironmentProviders).ɵproviders;
}

/**
* Describes a function that is used to process provider lists (such as provider
* overrides).
Expand All @@ -349,7 +384,7 @@ export type ProcessProvidersFunction = (providers: Provider[]) => Provider[];
*/
export interface ModuleWithProviders<T> {
ngModule: Type<T>;
providers?: Provider[];
providers?: Array<Provider|EnvironmentProviders>;
}

/**
Expand All @@ -364,8 +399,6 @@ export interface ModuleWithProviders<T> {
* @see `importProvidersFrom`
*
* @publicApi
* @developerPreview
* @deprecated replaced by `EnvironmentProviders`
*/
export interface ImportedNgModuleProviders {
ɵproviders: Provider[];
}
export type ImportedNgModuleProviders = EnvironmentProviders;
48 changes: 38 additions & 10 deletions packages/core/src/di/provider_collection.ts
Expand Up @@ -21,9 +21,19 @@ 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, 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';

/**
* Wrap an array of `Provider`s into `EnvironmentProviders`, preventing them from being accidentally
* referenced in `@Component in a component injector.
Comment on lines +28 to +29
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggested change
* Wrap an array of `Provider`s into `EnvironmentProviders`, preventing them from being accidentally
* referenced in `@Component in a component injector.
* Wrap an array of `Provider`s into `EnvironmentProviders`, preventing them from being accidentally
* referenced in `@Component` or `@Directive` in a component injector.

*/
export function makeEnvironmentProviders(providers: Provider[]): EnvironmentProviders {
return {
ɵproviders: providers,
} as unknown as EnvironmentProviders;
}

/**
* A source of providers for the `importProvidersFrom` function.
*
Expand Down Expand Up @@ -74,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 Expand Up @@ -118,7 +130,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);
});
Expand Down Expand Up @@ -251,12 +263,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 => {
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Just curious if we can type it here (vs adding as)?

Suggested change
deepForEachProvider(defProviders, provider => {
deepForEachProvider(defProviders, (provider: SingleProvider) => {

ngDevMode && validateProvider(provider as SingleProvider, defProviders, injectorType);
providersOut.push(provider as SingleProvider);
});
}
} else {
Expand All @@ -270,7 +282,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;
Expand All @@ -284,6 +297,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});

Expand Down