feat(ivy): support providers and viewProviders (#25803)
PR Close #25803
This commit is contained in:

committed by
Matias Niemelä

parent
9dc52d9d04
commit
b0476f308b
@ -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;
|
||||
}
|
||||
|
@ -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++) {
|
||||
|
@ -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';
|
||||
}
|
||||
|
||||
|
Reference in New Issue
Block a user