feat(ivy): @NgModule -> ngInjectorDef compilation (#22458)
This adds compilation of @NgModule providers and imports into ngInjectorDef statements in generated code. All @NgModule annotations will be compiled and the @NgModule decorators removed from the resultant js output. All @Injectables will also be compiled in Ivy mode, and the decorator removed. PR Close #22458
This commit is contained in:

committed by
Miško Hevery

parent
688096b7a3
commit
6ef9f2278f
@ -13,13 +13,13 @@
|
||||
*/
|
||||
|
||||
export * from './di/metadata';
|
||||
export {defineInjectable, Injectable, InjectableDecorator, InjectableProvider, InjectableType} from './di/injectable';
|
||||
|
||||
export * from './di/defs';
|
||||
export {forwardRef, resolveForwardRef, ForwardRefFn} from './di/forward_ref';
|
||||
|
||||
export {inject, InjectFlags, Injector} from './di/injector';
|
||||
export {Injectable, InjectableDecorator, InjectableProvider} from './di/injectable';
|
||||
export {inject, InjectFlags, INJECTOR, Injector} from './di/injector';
|
||||
export {ReflectiveInjector} from './di/reflective_injector';
|
||||
export {StaticProvider, ValueProvider, ExistingProvider, FactoryProvider, Provider, TypeProvider, ClassProvider} from './di/provider';
|
||||
export {createInjector} from './di/r3_injector';
|
||||
export {ResolvedReflectiveFactory, ResolvedReflectiveProvider} from './di/reflective_provider';
|
||||
export {ReflectiveKey} from './di/reflective_key';
|
||||
export {InjectionToken} from './di/injection_token';
|
||||
|
140
packages/core/src/di/defs.ts
Normal file
140
packages/core/src/di/defs.ts
Normal file
@ -0,0 +1,140 @@
|
||||
/**
|
||||
* @license
|
||||
* Copyright Google Inc. All Rights Reserved.
|
||||
*
|
||||
* Use of this source code is governed by an MIT-style license that can be
|
||||
* found in the LICENSE file at https://angular.io/license
|
||||
*/
|
||||
|
||||
import {Type} from '../type';
|
||||
|
||||
import {ClassProvider, ClassSansProvider, ConstructorProvider, ConstructorSansProvider, ExistingProvider, ExistingSansProvider, FactoryProvider, FactorySansProvider, StaticClassProvider, StaticClassSansProvider, ValueProvider, ValueSansProvider} from './provider';
|
||||
|
||||
/**
|
||||
* Information about how a type or `InjectionToken` interfaces with the DI system.
|
||||
*
|
||||
* At a minimum, this includes a `factory` which defines how to create the given type `T`, possibly
|
||||
* requesting injection of other types if necessary.
|
||||
*
|
||||
* Optionally, a `providedIn` parameter specifies that the given type belongs to a particular
|
||||
* `InjectorDef`, `NgModule`, or a special scope (e.g. `'root'`). A value of `null` indicates
|
||||
* that the injectable does not belong to any scope.
|
||||
*
|
||||
* This type is typically generated by the Angular compiler, but can be hand-written if needed.
|
||||
*
|
||||
* @experimental
|
||||
*/
|
||||
export interface InjectableDef<T> {
|
||||
providedIn: InjectorType<any>|'root'|'any'|null;
|
||||
factory: () => T;
|
||||
}
|
||||
|
||||
/**
|
||||
* Information about the providers to be included in an `Injector` as well as how the given type
|
||||
* which carries the information should be created by the DI system.
|
||||
*
|
||||
* An `InjectorDef` can import other types which have `InjectorDefs`, forming a deep nested
|
||||
* structure of providers with a defined priority (identically to how `NgModule`s also have
|
||||
* an import/dependency structure).
|
||||
*
|
||||
* @experimental
|
||||
*/
|
||||
export interface InjectorDef<T> {
|
||||
factory: () => T;
|
||||
|
||||
// TODO(alxhub): Narrow down the type here once decorators properly change the return type of the
|
||||
// class they are decorating (to add the ngInjectableDef property for example).
|
||||
providers: (Type<any>|ValueProvider|ExistingProvider|FactoryProvider|ConstructorProvider|
|
||||
StaticClassProvider|ClassProvider|any[])[];
|
||||
|
||||
imports: (InjectorType<any>|InjectorTypeWithProviders<any>)[];
|
||||
}
|
||||
|
||||
/**
|
||||
* A `Type` which has an `InjectableDef` static field.
|
||||
*
|
||||
* `InjectableDefType`s contain their own Dependency Injection metadata and are usable in an
|
||||
* `InjectorDef`-based `StaticInjector.
|
||||
*
|
||||
* @experimental
|
||||
*/
|
||||
export interface InjectableType<T> extends Type<T> { ngInjectableDef: InjectableDef<T>; }
|
||||
|
||||
/**
|
||||
* A type which has an `InjectorDef` static field.
|
||||
*
|
||||
* `InjectorDefTypes` can be used to configure a `StaticInjector`.
|
||||
*
|
||||
* @experimental
|
||||
*/
|
||||
export interface InjectorType<T> extends Type<T> { ngInjectorDef: InjectorDef<T>; }
|
||||
|
||||
/**
|
||||
* Describes the `InjectorDef` equivalent of a `ModuleWithProviders`, an `InjectorDefType` with an
|
||||
* associated array of providers.
|
||||
*
|
||||
* Objects of this type can be listed in the imports section of an `InjectorDef`.
|
||||
*
|
||||
* @experimental
|
||||
*/
|
||||
export interface InjectorTypeWithProviders<T> {
|
||||
ngModule: InjectorType<T>;
|
||||
providers?: (Type<any>|ValueProvider|ExistingProvider|FactoryProvider|ConstructorProvider|
|
||||
StaticClassProvider|ClassProvider|any[])[];
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Construct an `InjectableDef` which defines how a token will be constructed by the DI system, and
|
||||
* in which injectors (if any) it will be available.
|
||||
*
|
||||
* This should be assigned to a static `ngInjectableDef` field on a type, which will then be an
|
||||
* `InjectableType`.
|
||||
*
|
||||
* Options:
|
||||
* * `providedIn` determines which injectors will include the injectable, by either associating it
|
||||
* with an `@NgModule` or other `InjectorType`, or by specifying that this injectable should be
|
||||
* provided in the `'root'` injector, which will be the application-level injector in most apps.
|
||||
* * `factory` gives the zero argument function which will create an instance of the injectable.
|
||||
* The factory can call `inject` to access the `Injector` and request injection of dependencies.
|
||||
*
|
||||
* @experimental
|
||||
*/
|
||||
export function defineInjectable<T>(opts: {
|
||||
providedIn?: Type<any>| 'root' | null,
|
||||
factory: () => T,
|
||||
}): InjectableDef<T> {
|
||||
return {
|
||||
providedIn: (opts.providedIn as InjectorType<any>| 'root' | null | undefined) || null,
|
||||
factory: opts.factory,
|
||||
};
|
||||
}
|
||||
|
||||
/**
|
||||
* Construct an `InjectorDef` which configures an injector.
|
||||
*
|
||||
* This should be assigned to a static `ngInjectorDef` field on a type, which will then be an
|
||||
* `InjectorType`.
|
||||
*
|
||||
* Options:
|
||||
*
|
||||
* * `factory`: an `InjectorType` is an instantiable type, so a zero argument `factory` function to
|
||||
* create the type must be provided. If that factory function needs to inject arguments, it can
|
||||
* use the `inject` function.
|
||||
* * `providers`: an optional array of providers to add to the injector. Each provider must
|
||||
* either have a factory or point to a type which has an `ngInjectableDef` static property (the
|
||||
* type must be an `InjectableType`).
|
||||
* * `imports`: an optional array of imports of other `InjectorType`s or `InjectorTypeWithModule`s
|
||||
* whose providers will also be added to the injector. Locally provided types will override
|
||||
* providers from imports.
|
||||
*
|
||||
* @experimental
|
||||
*/
|
||||
export function defineInjector(options: {factory: () => any, providers?: any[], imports?: any[]}):
|
||||
InjectorDef<any> {
|
||||
return {
|
||||
factory: options.factory,
|
||||
providers: options.providers || [],
|
||||
imports: options.imports || [],
|
||||
};
|
||||
}
|
@ -11,6 +11,7 @@ import {Type} from '../type';
|
||||
import {makeDecorator, makeParamDecorator} from '../util/decorators';
|
||||
import {getClosureSafeProperty} from '../util/property';
|
||||
|
||||
import {InjectableDef, InjectableType, defineInjectable} from './defs';
|
||||
import {inject, injectArgs} from './injector';
|
||||
import {ClassSansProvider, ConstructorProvider, ConstructorSansProvider, ExistingProvider, ExistingSansProvider, FactoryProvider, FactorySansProvider, StaticClassProvider, StaticClassSansProvider, ValueProvider, ValueSansProvider} from './provider';
|
||||
|
||||
@ -108,22 +109,6 @@ export function convertInjectableProviderToFactory(
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Construct an `InjectableDef` which defines how a token will be constructed by the DI system, and
|
||||
* in which injectors (if any) it will be available.
|
||||
*
|
||||
* @experimental
|
||||
*/
|
||||
export function defineInjectable<T>(opts: {
|
||||
providedIn?: Type<any>| 'root' | null,
|
||||
factory: () => T,
|
||||
}): InjectableDef<T> {
|
||||
return {
|
||||
providedIn: opts.providedIn || null,
|
||||
factory: opts.factory,
|
||||
};
|
||||
}
|
||||
|
||||
/**
|
||||
* Injectable decorator and metadata.
|
||||
*
|
||||
@ -132,21 +117,16 @@ export function defineInjectable<T>(opts: {
|
||||
*/
|
||||
export const Injectable: InjectableDecorator = makeDecorator(
|
||||
'Injectable', undefined, undefined, undefined,
|
||||
(injectableType: Type<any>,
|
||||
(injectableType: InjectableType<any>,
|
||||
options: {providedIn?: Type<any>| 'root' | null} & InjectableProvider) => {
|
||||
if (options && options.providedIn) {
|
||||
(injectableType as InjectableType<any>).ngInjectableDef = defineInjectable({
|
||||
if (options && options.providedIn !== undefined) {
|
||||
injectableType.ngInjectableDef = defineInjectable({
|
||||
providedIn: options.providedIn,
|
||||
factory: convertInjectableProviderToFactory(injectableType, options)
|
||||
});
|
||||
}
|
||||
});
|
||||
|
||||
export interface InjectableDef<T> {
|
||||
providedIn: Type<any>|'root'|null;
|
||||
factory: () => T;
|
||||
}
|
||||
|
||||
/**
|
||||
* Type representing injectable service.
|
||||
*
|
||||
|
@ -8,7 +8,7 @@
|
||||
|
||||
import {Type} from '../type';
|
||||
|
||||
import {InjectableDef, defineInjectable} from './injectable';
|
||||
import {InjectableDef, defineInjectable} from './defs';
|
||||
|
||||
/**
|
||||
* Creates a token that can be used in a DI Provider.
|
||||
@ -26,8 +26,24 @@ import {InjectableDef, defineInjectable} from './injectable';
|
||||
* // myInterface is inferred to be MyInterface.
|
||||
* ```
|
||||
*
|
||||
* When creating an `InjectionToken`, you can optionally specify a factory function which returns
|
||||
* (possibly by creating) a default value of the parameterized type `T`. This sets up the
|
||||
* `InjectionToken` using this factory as a provider as if it was defined explicitly in the
|
||||
* application's root injector. If the factory function, which takes zero arguments, needs to inject
|
||||
* dependencies, it can do so using the `inject` function. See below for an example.
|
||||
*
|
||||
* Additionally, if a `factory` is specified you can also specify the `providedIn` option, which
|
||||
* overrides the above behavior and marks the token as belonging to a particular `@NgModule`. As
|
||||
* mentioned above, `'root'` is the default value for `providedIn`.
|
||||
*
|
||||
* ### Example
|
||||
*
|
||||
* #### Tree-shakeable InjectionToken
|
||||
*
|
||||
* {@example core/di/ts/injector_spec.ts region='ShakeableInjectionToken'}
|
||||
*
|
||||
* #### Plain InjectionToken
|
||||
*
|
||||
* {@example core/di/ts/injector_spec.ts region='InjectionToken'}
|
||||
*
|
||||
* @stable
|
||||
@ -54,3 +70,7 @@ export class InjectionToken<T> {
|
||||
|
||||
toString(): string { return `InjectionToken ${this._desc}`; }
|
||||
}
|
||||
|
||||
export interface InjectableDefToken<T> extends InjectionToken<T> {
|
||||
ngInjectableDef: InjectableDef<T>;
|
||||
}
|
||||
|
@ -8,6 +8,8 @@
|
||||
|
||||
import {Type} from '../type';
|
||||
import {stringify} from '../util';
|
||||
|
||||
import {InjectableDef, defineInjectable} from './defs';
|
||||
import {resolveForwardRef} from './forward_ref';
|
||||
import {InjectionToken} from './injection_token';
|
||||
import {Inject, Optional, Self, SkipSelf} from './metadata';
|
||||
@ -17,7 +19,17 @@ export const SOURCE = '__source';
|
||||
const _THROW_IF_NOT_FOUND = new Object();
|
||||
export const THROW_IF_NOT_FOUND = _THROW_IF_NOT_FOUND;
|
||||
|
||||
class _NullInjector implements Injector {
|
||||
/**
|
||||
* An InjectionToken that gets the current `Injector` for `createInjector()`-style injectors.
|
||||
*
|
||||
* Requesting this token instead of `Injector` allows `StaticInjector` to be tree-shaken from a
|
||||
* project.
|
||||
*
|
||||
* @experimental
|
||||
*/
|
||||
export const INJECTOR = new InjectionToken<Injector>('INJECTOR');
|
||||
|
||||
export class NullInjector implements Injector {
|
||||
get(token: any, notFoundValue: any = _THROW_IF_NOT_FOUND): any {
|
||||
if (notFoundValue === _THROW_IF_NOT_FOUND) {
|
||||
throw new Error(`NullInjectorError: No provider for ${stringify(token)}!`);
|
||||
@ -48,7 +60,7 @@ class _NullInjector implements Injector {
|
||||
*/
|
||||
export abstract class Injector {
|
||||
static THROW_IF_NOT_FOUND = _THROW_IF_NOT_FOUND;
|
||||
static NULL: Injector = new _NullInjector();
|
||||
static NULL: Injector = new NullInjector();
|
||||
|
||||
/**
|
||||
* Retrieves an instance from the injector based on the provided token.
|
||||
@ -87,6 +99,11 @@ export abstract class Injector {
|
||||
return new StaticInjector(options.providers, options.parent, options.name || null);
|
||||
}
|
||||
}
|
||||
|
||||
static ngInjectableDef = defineInjectable({
|
||||
providedIn: 'any' as any,
|
||||
factory: () => inject(INJECTOR),
|
||||
});
|
||||
}
|
||||
|
||||
|
||||
@ -100,7 +117,7 @@ const MULTI_PROVIDER_FN = function(): any[] {
|
||||
return Array.prototype.slice.call(arguments);
|
||||
};
|
||||
const GET_PROPERTY_NAME = {} as any;
|
||||
const USE_VALUE =
|
||||
export const USE_VALUE =
|
||||
getClosureSafeProperty<ValueProvider>({provide: String, useValue: GET_PROPERTY_NAME});
|
||||
const NG_TOKEN_PATH = 'ngTokenPath';
|
||||
const NG_TEMP_TOKEN_PATH = 'ngTempTokenPath';
|
||||
@ -127,6 +144,8 @@ export class StaticInjector implements Injector {
|
||||
const records = this._records = new Map<any, Record>();
|
||||
records.set(
|
||||
Injector, <Record>{token: Injector, fn: IDENT, deps: EMPTY, value: this, useNew: false});
|
||||
records.set(
|
||||
INJECTOR, <Record>{token: Injector, fn: IDENT, deps: EMPTY, value: this, useNew: false});
|
||||
recursivelyProcessProviders(records, providers);
|
||||
}
|
||||
|
||||
|
408
packages/core/src/di/r3_injector.ts
Normal file
408
packages/core/src/di/r3_injector.ts
Normal file
@ -0,0 +1,408 @@
|
||||
/**
|
||||
* @license
|
||||
* Copyright Google Inc. All Rights Reserved.
|
||||
*
|
||||
* Use of this source code is governed by an MIT-style license that can be
|
||||
* found in the LICENSE file at https://angular.io/license
|
||||
*/
|
||||
|
||||
import {OnDestroy} from '../metadata/lifecycle_hooks';
|
||||
import {Type} from '../type';
|
||||
import {stringify} from '../util';
|
||||
|
||||
import {InjectableDef, InjectableType, InjectorDef, InjectorType, InjectorTypeWithProviders} from './defs';
|
||||
import {resolveForwardRef} from './forward_ref';
|
||||
import {InjectableDefToken, InjectionToken} from './injection_token';
|
||||
import {INJECTOR, InjectFlags, Injector, NullInjector, THROW_IF_NOT_FOUND, USE_VALUE, inject, injectArgs, setCurrentInjector} from './injector';
|
||||
import {ClassProvider, ConstructorProvider, ExistingProvider, FactoryProvider, Provider, StaticClassProvider, TypeProvider, ValueProvider} from './provider';
|
||||
import {APP_ROOT} from './scope';
|
||||
|
||||
|
||||
|
||||
/**
|
||||
* Internal type for a single provider in a deep provider array.
|
||||
*/
|
||||
type SingleProvider = TypeProvider | ValueProvider | ClassProvider | ConstructorProvider |
|
||||
ExistingProvider | FactoryProvider | StaticClassProvider;
|
||||
|
||||
/**
|
||||
* Marker which indicates that a value has not yet been created from the factory function.
|
||||
*/
|
||||
const NOT_YET = {};
|
||||
|
||||
/**
|
||||
* Marker which indicates that the factory function for a token is in the process of being called.
|
||||
*
|
||||
* If the injector is asked to inject a token with its value set to CIRCULAR, that indicates
|
||||
* injection of a dependency has recursively attempted to inject the original token, and there is
|
||||
* a circular dependency among the providers.
|
||||
*/
|
||||
const CIRCULAR = {};
|
||||
|
||||
const EMPTY_ARRAY = [] as any[];
|
||||
|
||||
/**
|
||||
* A lazily initialized NullInjector.
|
||||
*/
|
||||
let NULL_INJECTOR: Injector|undefined = undefined;
|
||||
|
||||
function getNullInjector(): Injector {
|
||||
if (NULL_INJECTOR === undefined) {
|
||||
NULL_INJECTOR = new NullInjector();
|
||||
}
|
||||
return NULL_INJECTOR;
|
||||
}
|
||||
|
||||
/**
|
||||
* An entry in the injector which tracks information about the given token, including a possible
|
||||
* current value.
|
||||
*/
|
||||
interface Record<T> {
|
||||
factory: (() => T)|undefined;
|
||||
value: T|{};
|
||||
multi: any[]|undefined;
|
||||
}
|
||||
|
||||
/**
|
||||
* Create a new `Injector` which is configured using `InjectorDefType`s.
|
||||
*
|
||||
* @experimental
|
||||
*/
|
||||
export function createInjector(
|
||||
defType: /* InjectorDefType<any> */ any, parent: Injector | null = null): Injector {
|
||||
parent = parent || getNullInjector();
|
||||
return new R3Injector(defType, parent);
|
||||
}
|
||||
|
||||
export class R3Injector {
|
||||
/**
|
||||
* Map of tokens to records which contain the instances of those tokens.
|
||||
*/
|
||||
private records = new Map<Type<any>|InjectionToken<any>, Record<any>>();
|
||||
|
||||
/**
|
||||
* The transitive set of `InjectorDefType`s which define this injector.
|
||||
*/
|
||||
private injectorDefTypes = new Set<InjectorType<any>>();
|
||||
|
||||
/**
|
||||
* Set of values instantiated by this injector which contain `ngOnDestroy` lifecycle hooks.
|
||||
*/
|
||||
private onDestroy = new Set<OnDestroy>();
|
||||
|
||||
/**
|
||||
* Flag indicating this injector provides the APP_ROOT_SCOPE token, and thus counts as the
|
||||
* root scope.
|
||||
*/
|
||||
private readonly isRootInjector: boolean;
|
||||
|
||||
/**
|
||||
* Flag indicating that this injector was previously destroyed.
|
||||
*/
|
||||
private destroyed = false;
|
||||
|
||||
constructor(def: InjectorType<any>, readonly parent: Injector) {
|
||||
// Start off by creating Records for every provider declared in every InjectorDefType
|
||||
// included transitively in `def`.
|
||||
deepForEach(
|
||||
[def], injectorDef => this.processInjectorType(injectorDef, new Set<InjectorType<any>>()));
|
||||
|
||||
// Make sure the INJECTOR token provides this injector.
|
||||
this.records.set(INJECTOR, makeRecord(undefined, this));
|
||||
|
||||
// Detect whether this injector has the APP_ROOT_SCOPE token and thus should provide
|
||||
// any injectable scoped to APP_ROOT_SCOPE.
|
||||
this.isRootInjector = this.records.has(APP_ROOT);
|
||||
|
||||
// Eagerly instantiate the InjectorDefType classes themselves.
|
||||
this.injectorDefTypes.forEach(defType => this.get(defType));
|
||||
}
|
||||
|
||||
/**
|
||||
* Destroy the injector and release references to every instance or provider associated with it.
|
||||
*
|
||||
* Also calls the `OnDestroy` lifecycle hooks of every instance that was created for which a
|
||||
* hook was found.
|
||||
*/
|
||||
destroy(): void {
|
||||
this.assertNotDestroyed();
|
||||
|
||||
// Set destroyed = true first, in case lifecycle hooks re-enter destroy().
|
||||
this.destroyed = true;
|
||||
try {
|
||||
// Call all the lifecycle hooks.
|
||||
this.onDestroy.forEach(service => service.ngOnDestroy());
|
||||
} finally {
|
||||
// Release all references.
|
||||
this.records.clear();
|
||||
this.onDestroy.clear();
|
||||
this.injectorDefTypes.clear();
|
||||
}
|
||||
}
|
||||
|
||||
get<T>(
|
||||
token: Type<T>|InjectionToken<T>, notFoundValue: any = THROW_IF_NOT_FOUND,
|
||||
flags = InjectFlags.Default): T {
|
||||
this.assertNotDestroyed();
|
||||
// Set the injection context.
|
||||
const previousInjector = setCurrentInjector(this);
|
||||
try {
|
||||
// Check for the SkipSelf flag.
|
||||
if (!(flags & InjectFlags.SkipSelf)) {
|
||||
// SkipSelf isn't set, check if the record belongs to this injector.
|
||||
let record: Record<T>|undefined = this.records.get(token);
|
||||
if (record === undefined) {
|
||||
// No record, but maybe the token is scoped to this injector. Look for an ngInjectableDef
|
||||
// with a scope matching this injector.
|
||||
const def = couldBeInjectableType(token) &&
|
||||
(token as InjectableType<any>| InjectableDefToken<any>).ngInjectableDef ||
|
||||
undefined;
|
||||
if (def !== undefined && this.injectableDefInScope(def)) {
|
||||
// Found an ngInjectableDef and it's scoped to this injector. Pretend as if it was here
|
||||
// all along.
|
||||
record = injectableDefRecord(token);
|
||||
this.records.set(token, record);
|
||||
}
|
||||
}
|
||||
// If a record was found, get the instance for it and return it.
|
||||
if (record !== undefined) {
|
||||
return this.hydrate(token, record);
|
||||
}
|
||||
}
|
||||
|
||||
// Select the next injector based on the Self flag - if self is set, the next injector is
|
||||
// the NullInjector, otherwise it's the parent.
|
||||
let next = !(flags & InjectFlags.Self) ? this.parent : getNullInjector();
|
||||
return this.parent.get(token, notFoundValue);
|
||||
} finally {
|
||||
// Lastly, clean up the state by restoring the previous injector.
|
||||
setCurrentInjector(previousInjector);
|
||||
}
|
||||
}
|
||||
|
||||
private assertNotDestroyed(): void {
|
||||
if (this.destroyed) {
|
||||
throw new Error('Injector has already been destroyed.');
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Add an `InjectorDefType` or `InjectorDefTypeWithProviders` and all of its transitive providers
|
||||
* to this injector.
|
||||
*/
|
||||
private processInjectorType(
|
||||
defOrWrappedDef: InjectorType<any>|InjectorTypeWithProviders<any>,
|
||||
parents: Set<InjectorType<any>>) {
|
||||
defOrWrappedDef = resolveForwardRef(defOrWrappedDef);
|
||||
|
||||
// Either the defOrWrappedDef is an InjectorDefType (with ngInjectorDef) or an
|
||||
// InjectorDefTypeWithProviders (aka ModuleWithProviders). Detecting either is a megamorphic
|
||||
// read, so care is taken to only do the read once.
|
||||
|
||||
// First attempt to read the ngInjectorDef.
|
||||
let def = (defOrWrappedDef as InjectorType<any>).ngInjectorDef as(InjectorDef<any>| undefined);
|
||||
|
||||
// If that's not present, then attempt to read ngModule from the InjectorDefTypeWithProviders.
|
||||
const ngModule =
|
||||
(def == null) && (defOrWrappedDef as InjectorTypeWithProviders<any>).ngModule || undefined;
|
||||
|
||||
// Determine the InjectorDefType. In the case where `defOrWrappedDef` is an `InjectorDefType`,
|
||||
// then this is easy. In the case of an InjectorDefTypeWithProviders, then the definition type
|
||||
// is the `ngModule`.
|
||||
const defType: InjectorType<any> =
|
||||
(ngModule === undefined) ? (defOrWrappedDef as InjectorType<any>) : ngModule;
|
||||
|
||||
// If defOrWrappedType was an InjectorDefTypeWithProviders, then .providers may hold some
|
||||
// extra providers.
|
||||
const providers =
|
||||
(ngModule !== undefined) && (defOrWrappedDef as InjectorTypeWithProviders<any>).providers ||
|
||||
EMPTY_ARRAY;
|
||||
|
||||
// Finally, if defOrWrappedType was an `InjectorDefTypeWithProviders`, then the actual
|
||||
// `InjectorDef` is on its `ngModule`.
|
||||
if (ngModule !== undefined) {
|
||||
def = ngModule.ngInjectorDef;
|
||||
}
|
||||
|
||||
// If no definition was found, throw.
|
||||
if (def == null) {
|
||||
throw new Error(`Type ${stringify(defType)} is missing an ngInjectorDef definition.`);
|
||||
}
|
||||
|
||||
// Check for circular dependencies.
|
||||
if (parents.has(defType)) {
|
||||
throw new Error(`Circular dependency: type ${stringify(defType)} ends up importing itself.`);
|
||||
}
|
||||
|
||||
// Track the InjectorDefType and add a provider for it.
|
||||
this.injectorDefTypes.add(defType);
|
||||
this.records.set(defType, makeRecord(def.factory));
|
||||
|
||||
// Add providers in the same way that @NgModule resolution did:
|
||||
|
||||
// First, include providers from any imports.
|
||||
if (def.imports != null) {
|
||||
// Before processing defType's imports, add it to the set of parents. This way, if it ends
|
||||
// up deeply importing itself, this can be detected.
|
||||
parents.add(defType);
|
||||
try {
|
||||
deepForEach(def.imports, imported => this.processInjectorType(imported, parents));
|
||||
} finally {
|
||||
// Remove it from the parents set when finished.
|
||||
parents.delete(defType);
|
||||
}
|
||||
}
|
||||
|
||||
// Next, include providers listed on the definition itself.
|
||||
if (def.providers != null) {
|
||||
deepForEach(def.providers, provider => this.processProvider(provider));
|
||||
}
|
||||
|
||||
// Finally, include providers from an InjectorDefTypeWithProviders if there was one.
|
||||
deepForEach(providers, provider => this.processProvider(provider));
|
||||
}
|
||||
|
||||
/**
|
||||
* Process a `SingleProvider` and add it.
|
||||
*/
|
||||
private processProvider(provider: SingleProvider): void {
|
||||
// Determine the token from the provider. Either it's its own token, or has a {provide: ...}
|
||||
// property.
|
||||
provider = resolveForwardRef(provider);
|
||||
let token: any = isTypeProvider(provider) ? provider : resolveForwardRef(provider.provide);
|
||||
|
||||
// Construct a `Record` for the provider.
|
||||
const record = providerToRecord(provider);
|
||||
|
||||
if (!isTypeProvider(provider) && provider.multi === true) {
|
||||
// If the provider indicates that it's a multi-provider, process it specially.
|
||||
// First check whether it's been defined already.
|
||||
let multiRecord = this.records.get(token);
|
||||
if (multiRecord) {
|
||||
// It has. Throw a nice error if
|
||||
if (multiRecord.multi === undefined) {
|
||||
throw new Error(`Mixed multi-provider for ${token}.`);
|
||||
}
|
||||
} else {
|
||||
token = provider;
|
||||
multiRecord = makeRecord(undefined, NOT_YET, true);
|
||||
multiRecord.factory = () => injectArgs(multiRecord !.multi !);
|
||||
this.records.set(token, multiRecord);
|
||||
}
|
||||
token = provider;
|
||||
multiRecord.multi !.push(provider);
|
||||
}
|
||||
|
||||
const existing = this.records.get(token);
|
||||
if (existing && existing.multi !== undefined) {
|
||||
throw new Error(`Mixed multi-provider for ${token}`);
|
||||
}
|
||||
|
||||
this.records.set(token, record);
|
||||
}
|
||||
|
||||
private hydrate<T>(token: Type<T>|InjectionToken<T>, record: Record<T>): T {
|
||||
if (record.value === CIRCULAR) {
|
||||
throw new Error(`Circular dep for ${stringify(token)}`);
|
||||
} else if (record.value === NOT_YET) {
|
||||
record.value = CIRCULAR;
|
||||
record.value = record.factory !();
|
||||
}
|
||||
if (typeof record.value === 'object' && record.value && hasOnDestroy(record.value)) {
|
||||
this.onDestroy.add(record.value);
|
||||
}
|
||||
return record.value as T;
|
||||
}
|
||||
|
||||
private injectableDefInScope(def: InjectableDef<any>): boolean {
|
||||
if (!def.providedIn) {
|
||||
return false;
|
||||
} else if (typeof def.providedIn === 'string') {
|
||||
return def.providedIn === 'any' || (def.providedIn === 'root' && this.isRootInjector);
|
||||
} else {
|
||||
return this.injectorDefTypes.has(def.providedIn);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
function injectableDefRecord(token: Type<any>| InjectionToken<any>): Record<any> {
|
||||
const def = (token as InjectableType<any>).ngInjectableDef;
|
||||
if (def === undefined) {
|
||||
throw new Error(`Type ${stringify(token)} is missing an ngInjectableDef definition.`);
|
||||
}
|
||||
return makeRecord(def.factory);
|
||||
}
|
||||
|
||||
function providerToRecord(provider: SingleProvider): Record<any> {
|
||||
let token = resolveForwardRef(provider);
|
||||
let value: any = NOT_YET;
|
||||
let factory: (() => any)|undefined = undefined;
|
||||
if (isTypeProvider(provider)) {
|
||||
return injectableDefRecord(provider);
|
||||
} else {
|
||||
token = resolveForwardRef(provider.provide);
|
||||
if (isValueProvider(provider)) {
|
||||
value = provider.useValue;
|
||||
} else if (isExistingProvider(provider)) {
|
||||
factory = () => inject(provider.useExisting);
|
||||
} else if (isFactoryProvider(provider)) {
|
||||
factory = () => provider.useFactory(...injectArgs(provider.deps || []));
|
||||
} else {
|
||||
const classRef = (provider as StaticClassProvider | ClassProvider).useClass || token;
|
||||
if (hasDeps(provider)) {
|
||||
factory = () => new (classRef)(...injectArgs(provider.deps));
|
||||
} else {
|
||||
return injectableDefRecord(classRef);
|
||||
}
|
||||
}
|
||||
}
|
||||
return makeRecord(factory, value);
|
||||
}
|
||||
|
||||
function makeRecord<T>(
|
||||
factory: (() => T) | undefined, value: T | {} = NOT_YET, multi: boolean = false): Record<T> {
|
||||
return {
|
||||
factory: factory,
|
||||
value: value,
|
||||
multi: multi ? [] : undefined,
|
||||
};
|
||||
}
|
||||
|
||||
function deepForEach<T>(input: (T | any[])[], fn: (value: T) => void): void {
|
||||
input.forEach(value => Array.isArray(value) ? deepForEach(value, fn) : fn(value));
|
||||
}
|
||||
|
||||
function isValueProvider(value: SingleProvider): value is ValueProvider {
|
||||
return USE_VALUE in value;
|
||||
}
|
||||
|
||||
function isExistingProvider(value: SingleProvider): value is ExistingProvider {
|
||||
return !!(value as ExistingProvider).useExisting;
|
||||
}
|
||||
|
||||
function isFactoryProvider(value: SingleProvider): value is FactoryProvider {
|
||||
return !!(value as FactoryProvider).useFactory;
|
||||
}
|
||||
|
||||
function isClassProvider(value: SingleProvider): value is ClassProvider {
|
||||
return !!(value as ClassProvider).useClass;
|
||||
}
|
||||
|
||||
function isTypeProvider(value: SingleProvider): value is TypeProvider {
|
||||
return typeof value === 'function';
|
||||
}
|
||||
|
||||
function hasDeps(value: ClassProvider | ConstructorProvider | StaticClassProvider):
|
||||
value is ClassProvider&{deps: any[]} {
|
||||
return !!(value as any).deps;
|
||||
}
|
||||
|
||||
function hasOnDestroy(value: any): value is OnDestroy {
|
||||
return typeof value === 'object' && value != null && (value as OnDestroy).ngOnDestroy &&
|
||||
typeof(value as OnDestroy).ngOnDestroy === 'function';
|
||||
}
|
||||
|
||||
function couldBeInjectableType(value: any): value is Type<any>|InjectionToken<any> {
|
||||
return (typeof value === 'function') ||
|
||||
(typeof value === 'object' && value instanceof InjectionToken);
|
||||
}
|
@ -6,10 +6,13 @@
|
||||
* found in the LICENSE file at https://angular.io/license
|
||||
*/
|
||||
|
||||
import {Provider} from '../di';
|
||||
import {InjectorDef, InjectorType, defineInjector} from '../di/defs';
|
||||
import {convertInjectableProviderToFactory} from '../di/injectable';
|
||||
import {Provider} from '../di/provider';
|
||||
import {Type} from '../type';
|
||||
import {TypeDecorator, makeDecorator} from '../util/decorators';
|
||||
|
||||
|
||||
/**
|
||||
* A wrapper around a module that also includes the providers.
|
||||
*
|
||||
@ -190,5 +193,17 @@ export interface NgModule {
|
||||
* @stable
|
||||
* @Annotation
|
||||
*/
|
||||
export const NgModule: NgModuleDecorator =
|
||||
makeDecorator('NgModule', (ngModule: NgModule) => ngModule);
|
||||
export const NgModule: NgModuleDecorator = makeDecorator(
|
||||
'NgModule', (ngModule: NgModule) => ngModule, undefined, undefined,
|
||||
(moduleType: InjectorType<any>, metadata: NgModule) => {
|
||||
let imports = (metadata && metadata.imports) || [];
|
||||
if (metadata && metadata.exports) {
|
||||
imports = [...imports, metadata.exports];
|
||||
}
|
||||
|
||||
moduleType.ngInjectorDef = defineInjector({
|
||||
factory: convertInjectableProviderToFactory(moduleType, {useClass: moduleType}),
|
||||
providers: metadata && metadata.providers,
|
||||
imports: imports,
|
||||
});
|
||||
});
|
||||
|
@ -6,9 +6,9 @@
|
||||
* found in the LICENSE file at https://angular.io/license
|
||||
*/
|
||||
|
||||
import {InjectableDef} from '../di/defs';
|
||||
import {resolveForwardRef} from '../di/forward_ref';
|
||||
import {InjectableDef} from '../di/injectable';
|
||||
import {InjectFlags, Injector, setCurrentInjector} from '../di/injector';
|
||||
import {INJECTOR, InjectFlags, Injector, setCurrentInjector} from '../di/injector';
|
||||
import {APP_ROOT} from '../di/scope';
|
||||
import {NgModuleRef} from '../linker/ng_module_factory';
|
||||
import {stringify} from '../util';
|
||||
|
@ -8,7 +8,8 @@
|
||||
|
||||
import {isDevMode} from '../application_ref';
|
||||
import {DebugElement, DebugNode, EventListener, getDebugNode, indexDebugNode, removeDebugNodeFromIndex} from '../debug/debug_node';
|
||||
import {InjectableType, Injector} from '../di';
|
||||
import {Injector} from '../di';
|
||||
import {InjectableType} from '../di/injectable';
|
||||
import {ErrorHandler} from '../error_handler';
|
||||
import {ComponentFactory} from '../linker/component_factory';
|
||||
import {NgModuleRef} from '../linker/ng_module_factory';
|
||||
|
Reference in New Issue
Block a user