feat(ivy): support providers and viewProviders (#25803)

PR Close #25803
This commit is contained in:
Marc Laval
2018-10-18 09:23:18 +02:00
committed by Matias Niemelä
parent 9dc52d9d04
commit b0476f308b
76 changed files with 4098 additions and 1645 deletions

View File

@ -8,6 +8,7 @@
import {Type} from '../type';
import {stringify} from '../util';
import {getClosureSafeProperty} from '../util/property';
@ -22,6 +23,8 @@ import {stringify} from '../util';
*/
export interface ForwardRefFn { (): any; }
const __forward_ref__ = getClosureSafeProperty({__forward_ref__: getClosureSafeProperty});
/**
* Allows to refer to references which are not yet defined.
*
@ -53,10 +56,11 @@ export function forwardRef(forwardRefFn: ForwardRefFn): Type<any> {
* @see `forwardRef`
* @publicApi
*/
export function resolveForwardRef(type: any): any {
if (typeof type === 'function' && type.hasOwnProperty('__forward_ref__') &&
type.__forward_ref__ === forwardRef) {
return (<ForwardRefFn>type)();
export function resolveForwardRef<T>(type: T): T {
const fn: any = type;
if (typeof fn === 'function' && fn.hasOwnProperty(__forward_ref__) &&
fn.__forward_ref__ === forwardRef) {
return fn();
} else {
return type;
}

View File

@ -431,6 +431,17 @@ export function setCurrentInjector(injector: Injector | null | undefined): Injec
_currentInjector = injector;
return former;
}
/**
* Current implementation of inject.
*
* By default, it is `injectInjectorOnly`, which makes it `Injector`-only aware. It can be changed
* to `directiveInject`, which brings in the `NodeInjector` system of ivy. It is designed this
* way for two reasons:
* 1. `Injector` should not depend on ivy logic.
* 2. To maintain tree shake-ability we don't want to bring in unnecessary code.
*/
let _injectImplementation: (<T>(token: Type<T>| InjectionToken<T>, flags: InjectFlags) => T | null)|
undefined;
/**
* Injects a token from the currently active injector.
@ -452,21 +463,53 @@ export function setCurrentInjector(injector: Injector | null | undefined): Injec
export function inject<T>(token: Type<T>| InjectionToken<T>): T;
export function inject<T>(token: Type<T>| InjectionToken<T>, flags?: InjectFlags): T|null;
export function inject<T>(token: Type<T>| InjectionToken<T>, flags = InjectFlags.Default): T|null {
return (_injectImplementation || injectInjectorOnly)(token, flags);
}
/**
* Sets the current inject implementation.
*/
export function setInjectImplementation(
impl: (<T>(token: Type<T>| InjectionToken<T>, flags?: InjectFlags) => T | null) | undefined):
(<T>(token: Type<T>| InjectionToken<T>, flags?: InjectFlags) => T | null)|undefined {
const previous = _injectImplementation;
_injectImplementation = impl;
return previous;
}
export function injectInjectorOnly<T>(token: Type<T>| InjectionToken<T>): T;
export function injectInjectorOnly<T>(token: Type<T>| InjectionToken<T>, flags?: InjectFlags): T|
null;
export function injectInjectorOnly<T>(
token: Type<T>| InjectionToken<T>, flags = InjectFlags.Default): T|null {
if (_currentInjector === undefined) {
throw new Error(`inject() must be called from an injection context`);
} else if (_currentInjector === null) {
const injectableDef: InjectableDef<T>|null = getInjectableDef(token);
if (injectableDef && injectableDef.providedIn == 'root') {
return injectableDef.value === undefined ? injectableDef.value = injectableDef.factory() :
injectableDef.value;
}
if (flags & InjectFlags.Optional) return null;
throw new Error(`Injector: NOT_FOUND [${stringify(token)}]`);
return injectRootLimpMode(token, undefined, flags);
} else {
return _currentInjector.get(token, flags & InjectFlags.Optional ? null : undefined, flags);
}
}
/**
* Injects `root` tokens in limp mode.
*
* If no injector exists, we can still inject tree-shakable providers which have `providedIn` set to
* `"root"`. This is known as the limp mode injection. In such case the value is stored in the
* `InjectableDef`.
*/
export function injectRootLimpMode<T>(
token: Type<T>| InjectionToken<T>, notFoundValue: T | undefined, flags: InjectFlags): T|null {
const injectableDef: InjectableDef<T>|null = getInjectableDef(token);
if (injectableDef && injectableDef.providedIn == 'root') {
return injectableDef.value === undefined ? injectableDef.value = injectableDef.factory() :
injectableDef.value;
}
if (flags & InjectFlags.Optional) return null;
if (notFoundValue !== undefined) return notFoundValue;
throw new Error(`Injector: NOT_FOUND [${stringify(token)}]`);
}
export function injectArgs(types: (Type<any>| InjectionToken<any>| any[])[]): any[] {
const args: any[] = [];
for (let i = 0; i < types.length; i++) {

View File

@ -165,7 +165,7 @@ export class R3Injector {
if (def && this.injectableDefInScope(def)) {
// Found an ngInjectableDef and it's scoped to this injector. Pretend as if it was here
// all along.
record = injectableDefRecord(token);
record = makeRecord(injectableDefFactory(token), NOT_YET);
this.records.set(token, record);
}
}
@ -328,7 +328,7 @@ export class R3Injector {
}
}
function injectableDefRecord(token: Type<any>| InjectionToken<any>): Record<any> {
function injectableDefFactory(token: Type<any>| InjectionToken<any>): () => any {
const injectableDef = getInjectableDef(token as InjectableType<any>);
if (injectableDef === null) {
if (token instanceof InjectionToken) {
@ -336,21 +336,34 @@ function injectableDefRecord(token: Type<any>| InjectionToken<any>): Record<any>
}
// TODO(alxhub): there should probably be a strict mode which throws here instead of assuming a
// no-args constructor.
return makeRecord(() => new (token as Type<any>)());
return () => new (token as Type<any>)();
}
return makeRecord(injectableDef.factory);
return injectableDef.factory;
}
function providerToRecord(provider: SingleProvider): Record<any> {
let factory: (() => any)|undefined = providerToFactory(provider);
if (isValueProvider(provider)) {
return makeRecord(undefined, provider.useValue);
} else {
return makeRecord(factory, NOT_YET);
}
}
/**
* Converts a `SingleProvider` into a factory function.
*
* @param provider provider to convert to factory
*/
export function providerToFactory(provider: SingleProvider): () => any {
let token = resolveForwardRef(provider);
let value: any = NOT_YET;
let factory: (() => any)|undefined = undefined;
if (isTypeProvider(provider)) {
return injectableDefRecord(provider);
return injectableDefFactory(provider);
} else {
token = resolveForwardRef(provider.provide);
if (isValueProvider(provider)) {
value = provider.useValue;
factory = () => provider.useValue;
} else if (isExistingProvider(provider)) {
factory = () => inject(provider.useExisting);
} else if (isFactoryProvider(provider)) {
@ -360,11 +373,11 @@ function providerToRecord(provider: SingleProvider): Record<any> {
if (hasDeps(provider)) {
factory = () => new (classRef)(...injectArgs(provider.deps));
} else {
return injectableDefRecord(classRef);
return injectableDefFactory(classRef);
}
}
}
return makeRecord(factory, value);
return factory;
}
function makeRecord<T>(
@ -392,7 +405,7 @@ function isFactoryProvider(value: SingleProvider): value is FactoryProvider {
return !!(value as FactoryProvider).useFactory;
}
function isTypeProvider(value: SingleProvider): value is TypeProvider {
export function isTypeProvider(value: SingleProvider): value is TypeProvider {
return typeof value === 'function';
}