fix(core): Allow passing AbstractType to the inject function (#37958)

This is a type only change that replaces `Type<T>|InjectionToken<T>` with
`Type<T>|AbstractType<T>|InjectionToken<T>` in the injector.

PR Close #37958
This commit is contained in:
Mitchell Wills 2020-07-07 01:27:23 +00:00 committed by Andrew Kushnir
parent 453b32f4b9
commit a1b6ad07a8
11 changed files with 88 additions and 48 deletions

View File

@ -445,7 +445,7 @@ export declare class InjectionToken<T> {
} }
export declare abstract class Injector { export declare abstract class Injector {
abstract get<T>(token: Type<T> | InjectionToken<T> | AbstractType<T>, notFoundValue?: T, flags?: InjectFlags): T; abstract get<T>(token: Type<T> | AbstractType<T> | InjectionToken<T>, notFoundValue?: T, flags?: InjectFlags): T;
/** @deprecated */ abstract get(token: any, notFoundValue?: any): any; /** @deprecated */ abstract get(token: any, notFoundValue?: any): any;
static NULL: Injector; static NULL: Injector;
static THROW_IF_NOT_FOUND: {}; static THROW_IF_NOT_FOUND: {};
@ -676,8 +676,8 @@ export declare function ɵɵdefineInjectable<T>(opts: {
}): never; }): never;
/** @codeGenApi */ /** @codeGenApi */
export declare function ɵɵinject<T>(token: Type<T> | InjectionToken<T>): T; export declare function ɵɵinject<T>(token: Type<T> | AbstractType<T> | InjectionToken<T>): T;
export declare function ɵɵinject<T>(token: Type<T> | InjectionToken<T>, flags?: InjectFlags): T | null; export declare function ɵɵinject<T>(token: Type<T> | AbstractType<T> | InjectionToken<T>, flags?: InjectFlags): T | null;
/** @codeGenApi */ /** @codeGenApi */
export declare interface ɵɵInjectableDef<T> { export declare interface ɵɵInjectableDef<T> {

View File

@ -6,7 +6,7 @@
* found in the LICENSE file at https://angular.io/license * found in the LICENSE file at https://angular.io/license
*/ */
import {Type} from '../interface/type'; import {AbstractType, Type} from '../interface/type';
import {assertNotEqual} from '../util/assert'; import {assertNotEqual} from '../util/assert';
import {stringify} from '../util/stringify'; import {stringify} from '../util/stringify';
import {InjectionToken} from './injection_token'; import {InjectionToken} from './injection_token';
@ -23,7 +23,8 @@ import {InjectFlags} from './interface/injector';
* 1. `Injector` should not depend on ivy logic. * 1. `Injector` should not depend on ivy logic.
* 2. To maintain tree shake-ability we don't want to bring in unnecessary code. * 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)| let _injectImplementation:
(<T>(token: Type<T>|AbstractType<T>|InjectionToken<T>, flags?: InjectFlags) => T | null)|
undefined; undefined;
export function getInjectImplementation() { export function getInjectImplementation() {
return _injectImplementation; return _injectImplementation;
@ -34,8 +35,10 @@ export function getInjectImplementation() {
* Sets the current inject implementation. * Sets the current inject implementation.
*/ */
export function setInjectImplementation( export function setInjectImplementation(
impl: (<T>(token: Type<T>|InjectionToken<T>, flags?: InjectFlags) => T | null)| impl: (<T>(token: Type<T>|AbstractType<T>|InjectionToken<T>, flags?: InjectFlags) => T | null)|
undefined): (<T>(token: Type<T>|InjectionToken<T>, flags?: InjectFlags) => T | null)|undefined { undefined):
(<T>(token: Type<T>|AbstractType<T>|InjectionToken<T>, flags?: InjectFlags) => T | null)|
undefined {
const previous = _injectImplementation; const previous = _injectImplementation;
_injectImplementation = impl; _injectImplementation = impl;
return previous; return previous;
@ -50,7 +53,8 @@ export function setInjectImplementation(
* `InjectableDef`. * `InjectableDef`.
*/ */
export function injectRootLimpMode<T>( export function injectRootLimpMode<T>(
token: Type<T>|InjectionToken<T>, notFoundValue: T|undefined, flags: InjectFlags): T|null { token: Type<T>|AbstractType<T>|InjectionToken<T>, notFoundValue: T|undefined,
flags: InjectFlags): T|null {
const injectableDef: ɵɵInjectableDef<T>|null = getInjectableDef(token); const injectableDef: ɵɵInjectableDef<T>|null = getInjectableDef(token);
if (injectableDef && injectableDef.providedIn == 'root') { if (injectableDef && injectableDef.providedIn == 'root') {
return injectableDef.value === undefined ? injectableDef.value = injectableDef.factory() : return injectableDef.value === undefined ? injectableDef.value = injectableDef.factory() :
@ -70,7 +74,7 @@ export function injectRootLimpMode<T>(
* @param fn Function which it should not equal to * @param fn Function which it should not equal to
*/ */
export function assertInjectImplementationNotEqual( export function assertInjectImplementationNotEqual(
fn: (<T>(token: Type<T>|InjectionToken<T>, flags?: InjectFlags) => T | null)) { fn: (<T>(token: Type<T>|AbstractType<T>|InjectionToken<T>, flags?: InjectFlags) => T | null)) {
ngDevMode && ngDevMode &&
assertNotEqual(_injectImplementation, fn, 'Calling ɵɵinject would cause infinite recursion'); assertNotEqual(_injectImplementation, fn, 'Calling ɵɵinject would cause infinite recursion');
} }

View File

@ -67,9 +67,9 @@ export abstract class Injector {
* @throws When the `notFoundValue` is `undefined` or `Injector.THROW_IF_NOT_FOUND`. * @throws When the `notFoundValue` is `undefined` or `Injector.THROW_IF_NOT_FOUND`.
*/ */
abstract get<T>( abstract get<T>(
token: Type<T>|InjectionToken<T>|AbstractType<T>, notFoundValue?: T, flags?: InjectFlags): T; token: Type<T>|AbstractType<T>|InjectionToken<T>, notFoundValue?: T, flags?: InjectFlags): T;
/** /**
* @deprecated from v4.0.0 use Type<T> or InjectionToken<T> * @deprecated from v4.0.0 use Type<T>, AbstractType<T> or InjectionToken<T>
* @suppress {duplicate} * @suppress {duplicate}
*/ */
abstract get(token: any, notFoundValue?: any): any; abstract get(token: any, notFoundValue?: any): any;
@ -156,7 +156,8 @@ export class StaticInjector implements Injector {
this.scope = recursivelyProcessProviders(records, providers); this.scope = recursivelyProcessProviders(records, providers);
} }
get<T>(token: Type<T>|InjectionToken<T>, notFoundValue?: T, flags?: InjectFlags): T; get<T>(token: Type<T>|AbstractType<T>|InjectionToken<T>, notFoundValue?: T, flags?: InjectFlags):
T;
get(token: any, notFoundValue?: any): any; get(token: any, notFoundValue?: any): any;
get(token: any, notFoundValue?: any, flags: InjectFlags = InjectFlags.Default): any { get(token: any, notFoundValue?: any, flags: InjectFlags = InjectFlags.Default): any {
const records = this._records; const records = this._records;

View File

@ -8,7 +8,7 @@
import '../util/ng_dev_mode'; import '../util/ng_dev_mode';
import {Type} from '../interface/type'; import {AbstractType, Type} from '../interface/type';
import {getClosureSafeProperty} from '../util/property'; import {getClosureSafeProperty} from '../util/property';
import {stringify} from '../util/stringify'; import {stringify} from '../util/stringify';
import {resolveForwardRef} from './forward_ref'; import {resolveForwardRef} from './forward_ref';
@ -46,12 +46,11 @@ export function setCurrentInjector(injector: Injector|null|undefined): Injector|
return former; return former;
} }
export function injectInjectorOnly<T>(token: Type<T>|AbstractType<T>|InjectionToken<T>): T;
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>( export function injectInjectorOnly<T>(
token: Type<T>|InjectionToken<T>, flags = InjectFlags.Default): T|null { token: Type<T>|AbstractType<T>|InjectionToken<T>, flags?: InjectFlags): T|null;
export function injectInjectorOnly<T>(
token: Type<T>|AbstractType<T>|InjectionToken<T>, flags = InjectFlags.Default): T|null {
if (_currentInjector === undefined) { if (_currentInjector === undefined) {
throw new Error(`inject() must be called from an injection context`); throw new Error(`inject() must be called from an injection context`);
} else if (_currentInjector === null) { } else if (_currentInjector === null) {
@ -74,9 +73,11 @@ export function injectInjectorOnly<T>(
* @codeGenApi * @codeGenApi
* @publicApi This instruction has been emitted by ViewEngine for some time and is deployed to npm. * @publicApi This instruction has been emitted by ViewEngine for some time and is deployed to npm.
*/ */
export function ɵɵinject<T>(token: Type<T>|InjectionToken<T>): T; export function ɵɵinject<T>(token: Type<T>|AbstractType<T>|InjectionToken<T>): T;
export function ɵɵinject<T>(token: Type<T>|InjectionToken<T>, flags?: InjectFlags): T|null; export function ɵɵinject<T>(
export function ɵɵinject<T>(token: Type<T>|InjectionToken<T>, flags = InjectFlags.Default): T|null { token: Type<T>|AbstractType<T>|InjectionToken<T>, flags?: InjectFlags): T|null;
export function ɵɵinject<T>(
token: Type<T>|AbstractType<T>|InjectionToken<T>, flags = InjectFlags.Default): T|null {
return (getInjectImplementation() || injectInjectorOnly)(resolveForwardRef(token), flags); return (getInjectImplementation() || injectInjectorOnly)(resolveForwardRef(token), flags);
} }
@ -130,7 +131,6 @@ Please check that 1) the type for the parameter at index ${
*/ */
export const inject = ɵɵinject; export const inject = ɵɵinject;
export function injectArgs(types: (Type<any>|InjectionToken<any>|any[])[]): any[] { export function injectArgs(types: (Type<any>|InjectionToken<any>|any[])[]): any[] {
const args: any[] = []; const args: any[] = [];
for (let i = 0; i < types.length; i++) { for (let i = 0; i < types.length; i++) {

View File

@ -9,7 +9,7 @@
import '../util/ng_dev_mode'; import '../util/ng_dev_mode';
import {OnDestroy} from '../interface/lifecycle_hooks'; import {OnDestroy} from '../interface/lifecycle_hooks';
import {Type} from '../interface/type'; import {AbstractType, Type} from '../interface/type';
import {FactoryFn, getFactoryDef} from '../render3/definition_factory'; import {FactoryFn, getFactoryDef} from '../render3/definition_factory';
import {throwCyclicDependencyError, throwInvalidProviderError, throwMixedMultiProviderError} from '../render3/errors_di'; import {throwCyclicDependencyError, throwInvalidProviderError, throwMixedMultiProviderError} from '../render3/errors_di';
import {deepForEach, newArray} from '../util/array_utils'; import {deepForEach, newArray} from '../util/array_utils';
@ -103,7 +103,7 @@ export class R3Injector {
* - `null` value implies that we don't have the record. Used by tree-shakable injectors * - `null` value implies that we don't have the record. Used by tree-shakable injectors
* to prevent further searches. * to prevent further searches.
*/ */
private records = new Map<Type<any>|InjectionToken<any>, Record<any>|null>(); private records = new Map<Type<any>|AbstractType<any>|InjectionToken<any>, Record<any>|null>();
/** /**
* The transitive set of `InjectorType`s which define this injector. * The transitive set of `InjectorType`s which define this injector.
@ -181,7 +181,7 @@ export class R3Injector {
} }
get<T>( get<T>(
token: Type<T>|InjectionToken<T>, notFoundValue: any = THROW_IF_NOT_FOUND, token: Type<T>|AbstractType<T>|InjectionToken<T>, notFoundValue: any = THROW_IF_NOT_FOUND,
flags = InjectFlags.Default): T { flags = InjectFlags.Default): T {
this.assertNotDestroyed(); this.assertNotDestroyed();
// Set the injection context. // Set the injection context.
@ -404,7 +404,7 @@ export class R3Injector {
this.records.set(token, record); this.records.set(token, record);
} }
private hydrate<T>(token: Type<T>|InjectionToken<T>, record: Record<T>): T { private hydrate<T>(token: Type<T>|AbstractType<T>|InjectionToken<T>, record: Record<T>): T {
if (ngDevMode && record.value === CIRCULAR) { if (ngDevMode && record.value === CIRCULAR) {
throwCyclicDependencyError(stringify(token)); throwCyclicDependencyError(stringify(token));
} else if (record.value === NOT_YET) { } else if (record.value === NOT_YET) {
@ -428,7 +428,8 @@ export class R3Injector {
} }
} }
function injectableDefOrInjectorDefFactory(token: Type<any>|InjectionToken<any>): FactoryFn<any> { function injectableDefOrInjectorDefFactory(token: Type<any>|AbstractType<any>|
InjectionToken<any>): FactoryFn<any> {
// Most tokens will have an injectable def directly on them, which specifies a factory directly. // Most tokens will have an injectable def directly on them, which specifies a factory directly.
const injectableDef = getInjectableDef(token); const injectableDef = getInjectableDef(token);
const factory = injectableDef !== null ? injectableDef.factory : getFactoryDef(token); const factory = injectableDef !== null ? injectableDef.factory : getFactoryDef(token);
@ -564,7 +565,8 @@ function hasOnDestroy(value: any): value is OnDestroy {
typeof (value as OnDestroy).ngOnDestroy === 'function'; typeof (value as OnDestroy).ngOnDestroy === 'function';
} }
function couldBeInjectableType(value: any): value is Type<any>|InjectionToken<any> { function couldBeInjectableType(value: any): value is Type<any>|AbstractType<any>|
InjectionToken<any> {
return (typeof value === 'function') || return (typeof value === 'function') ||
(typeof value === 'object' && value instanceof InjectionToken); (typeof value === 'object' && value instanceof InjectionToken);
} }

View File

@ -10,7 +10,7 @@ import {ChangeDetectorRef as ViewEngine_ChangeDetectorRef} from '../change_detec
import {InjectionToken} from '../di/injection_token'; import {InjectionToken} from '../di/injection_token';
import {Injector} from '../di/injector'; import {Injector} from '../di/injector';
import {InjectFlags} from '../di/interface/injector'; import {InjectFlags} from '../di/interface/injector';
import {Type} from '../interface/type'; import {AbstractType, Type} from '../interface/type';
import {ComponentFactory as viewEngine_ComponentFactory, ComponentRef as viewEngine_ComponentRef} from '../linker/component_factory'; import {ComponentFactory as viewEngine_ComponentFactory, ComponentRef as viewEngine_ComponentRef} from '../linker/component_factory';
import {ComponentFactoryResolver as viewEngine_ComponentFactoryResolver} from '../linker/component_factory_resolver'; import {ComponentFactoryResolver as viewEngine_ComponentFactoryResolver} from '../linker/component_factory_resolver';
import {createElementRef, ElementRef as viewEngine_ElementRef} from '../linker/element_ref'; import {createElementRef, ElementRef as viewEngine_ElementRef} from '../linker/element_ref';
@ -80,7 +80,9 @@ export const SCHEDULER = new InjectionToken<((fn: () => void) => void)>('SCHEDUL
function createChainedInjector(rootViewInjector: Injector, moduleInjector: Injector): Injector { function createChainedInjector(rootViewInjector: Injector, moduleInjector: Injector): Injector {
return { return {
get: <T>(token: Type<T>|InjectionToken<T>, notFoundValue?: T, flags?: InjectFlags): T => { get: <T>(
token: Type<T>|AbstractType<T>|InjectionToken<T>, notFoundValue?: T,
flags?: InjectFlags): T => {
const value = rootViewInjector.get(token, NOT_FOUND_CHECK_ONLY_ELEMENT_INJECTOR as T, flags); const value = rootViewInjector.get(token, NOT_FOUND_CHECK_ONLY_ELEMENT_INJECTOR as T, flags);
if (value !== NOT_FOUND_CHECK_ONLY_ELEMENT_INJECTOR || if (value !== NOT_FOUND_CHECK_ONLY_ELEMENT_INJECTOR ||

View File

@ -13,7 +13,7 @@ import {Injector} from '../di/injector';
import {InjectorMarkers} from '../di/injector_marker'; import {InjectorMarkers} from '../di/injector_marker';
import {getInjectorDef} from '../di/interface/defs'; import {getInjectorDef} from '../di/interface/defs';
import {InjectFlags} from '../di/interface/injector'; import {InjectFlags} from '../di/interface/injector';
import {Type} from '../interface/type'; import {AbstractType, Type} from '../interface/type';
import {assertDefined, assertEqual, assertIndexInRange} from '../util/assert'; import {assertDefined, assertEqual, assertIndexInRange} from '../util/assert';
import {noSideEffects} from '../util/closure'; import {noSideEffects} from '../util/closure';
@ -347,7 +347,8 @@ export function injectAttributeImpl(tNode: TNode, attrNameToInject: string): str
function notFoundValueOrThrow<T>( function notFoundValueOrThrow<T>(
notFoundValue: T|null, token: Type<T>|InjectionToken<T>, flags: InjectFlags): T|null { notFoundValue: T|null, token: Type<T>|AbstractType<T>|InjectionToken<T>, flags: InjectFlags): T|
null {
if (flags & InjectFlags.Optional) { if (flags & InjectFlags.Optional) {
return notFoundValue; return notFoundValue;
} else { } else {
@ -365,8 +366,8 @@ function notFoundValueOrThrow<T>(
* @returns the value from the injector or throws an exception * @returns the value from the injector or throws an exception
*/ */
function lookupTokenUsingModuleInjector<T>( function lookupTokenUsingModuleInjector<T>(
lView: LView, token: Type<T>|InjectionToken<T>, flags: InjectFlags, notFoundValue?: any): T| lView: LView, token: Type<T>|AbstractType<T>|InjectionToken<T>, flags: InjectFlags,
null { notFoundValue?: any): T|null {
if (flags & InjectFlags.Optional && notFoundValue === undefined) { if (flags & InjectFlags.Optional && notFoundValue === undefined) {
// This must be set or the NullInjector will throw for optional deps // This must be set or the NullInjector will throw for optional deps
notFoundValue = null; notFoundValue = null;
@ -408,7 +409,7 @@ function lookupTokenUsingModuleInjector<T>(
* @returns the value from the injector, `null` when not found, or `notFoundValue` if provided * @returns the value from the injector, `null` when not found, or `notFoundValue` if provided
*/ */
export function getOrCreateInjectable<T>( export function getOrCreateInjectable<T>(
tNode: TDirectiveHostNode|null, lView: LView, token: Type<T>|InjectionToken<T>, tNode: TDirectiveHostNode|null, lView: LView, token: Type<T>|AbstractType<T>|InjectionToken<T>,
flags: InjectFlags = InjectFlags.Default, notFoundValue?: any): T|null { flags: InjectFlags = InjectFlags.Default, notFoundValue?: any): T|null {
if (tNode !== null) { if (tNode !== null) {
const bloomHash = bloomHashBitOrFactory(token); const bloomHash = bloomHashBitOrFactory(token);
@ -508,7 +509,7 @@ export function createNodeInjector(): Injector {
} }
function searchTokensOnInjector<T>( function searchTokensOnInjector<T>(
injectorIndex: number, lView: LView, token: Type<T>|InjectionToken<T>, injectorIndex: number, lView: LView, token: Type<T>|AbstractType<T>|InjectionToken<T>,
previousTView: TView|null, flags: InjectFlags, hostTElementNode: TNode|null) { previousTView: TView|null, flags: InjectFlags, hostTElementNode: TNode|null) {
const currentTView = lView[TVIEW]; const currentTView = lView[TVIEW];
const tNode = currentTView.data[injectorIndex + NodeInjectorOffset.TNODE] as TNode; const tNode = currentTView.data[injectorIndex + NodeInjectorOffset.TNODE] as TNode;
@ -555,7 +556,7 @@ function searchTokensOnInjector<T>(
* @returns Index of a found directive or provider, or null when none found. * @returns Index of a found directive or provider, or null when none found.
*/ */
export function locateDirectiveOrProvider<T>( export function locateDirectiveOrProvider<T>(
tNode: TNode, tView: TView, token: Type<T>|InjectionToken<T>|string, tNode: TNode, tView: TView, token: Type<T>|AbstractType<T>|InjectionToken<T>|string,
canAccessViewProviders: boolean, isHostSpecialCase: boolean|number): number|null { canAccessViewProviders: boolean, isHostSpecialCase: boolean|number): number|null {
const nodeProviderIndexes = tNode.providerIndexes; const nodeProviderIndexes = tNode.providerIndexes;
const tInjectables = tView.data; const tInjectables = tView.data;
@ -646,8 +647,8 @@ export function getNodeInjectable(
* @returns the matching bit to check in the bloom filter or `null` if the token is not known. * @returns the matching bit to check in the bloom filter or `null` if the token is not known.
* When the returned value is negative then it represents special values such as `Injector`. * When the returned value is negative then it represents special values such as `Injector`.
*/ */
export function bloomHashBitOrFactory(token: Type<any>|InjectionToken<any>|string): number|Function| export function bloomHashBitOrFactory(token: Type<any>|AbstractType<any>|InjectionToken<any>|
undefined { string): number|Function|undefined {
ngDevMode && assertDefined(token, 'token must be defined'); ngDevMode && assertDefined(token, 'token must be defined');
if (typeof token === 'string') { if (typeof token === 'string') {
return token.charCodeAt(0) || 0; return token.charCodeAt(0) || 0;

View File

@ -8,7 +8,7 @@
import {InjectFlags, InjectionToken, resolveForwardRef} from '../../di'; import {InjectFlags, InjectionToken, resolveForwardRef} from '../../di';
import {assertInjectImplementationNotEqual} from '../../di/inject_switch'; import {assertInjectImplementationNotEqual} from '../../di/inject_switch';
import {ɵɵinject} from '../../di/injector_compatibility'; import {ɵɵinject} from '../../di/injector_compatibility';
import {Type} from '../../interface/type'; import {AbstractType, Type} from '../../interface/type';
import {getOrCreateInjectable} from '../di'; import {getOrCreateInjectable} from '../di';
import {TDirectiveHostNode} from '../interfaces/node'; import {TDirectiveHostNode} from '../interfaces/node';
import {getCurrentTNode, getLView} from '../state'; import {getCurrentTNode, getLView} from '../state';
@ -37,10 +37,11 @@ import {getCurrentTNode, getLView} from '../state';
* *
* @codeGenApi * @codeGenApi
*/ */
export function ɵɵdirectiveInject<T>(token: Type<T>|InjectionToken<T>): T; export function ɵɵdirectiveInject<T>(token: Type<T>|AbstractType<T>|InjectionToken<T>): T;
export function ɵɵdirectiveInject<T>(token: Type<T>|InjectionToken<T>, flags: InjectFlags): T;
export function ɵɵdirectiveInject<T>( export function ɵɵdirectiveInject<T>(
token: Type<T>|InjectionToken<T>, flags = InjectFlags.Default): T|null { token: Type<T>|AbstractType<T>|InjectionToken<T>, flags: InjectFlags): T;
export function ɵɵdirectiveInject<T>(
token: Type<T>|AbstractType<T>|InjectionToken<T>, flags = InjectFlags.Default): T|null {
const lView = getLView(); const lView = getLView();
// Fall back to inject() if view hasn't been created. This situation can happen in tests // Fall back to inject() if view hasn't been created. This situation can happen in tests
// if inject utilities are used before bootstrapping. // if inject utilities are used before bootstrapping.

View File

@ -8,7 +8,7 @@
import {InjectionToken} from '../../di/injection_token'; import {InjectionToken} from '../../di/injection_token';
import {InjectFlags} from '../../di/interface/injector'; import {InjectFlags} from '../../di/interface/injector';
import {Type} from '../../interface/type'; import {AbstractType, Type} from '../../interface/type';
import {assertDefined, assertEqual} from '../../util/assert'; import {assertDefined, assertEqual} from '../../util/assert';
import {TDirectiveHostNode} from './node'; import {TDirectiveHostNode} from './node';
@ -176,7 +176,8 @@ export class NodeInjectorFactory {
/** /**
* The inject implementation to be activated when using the factory. * The inject implementation to be activated when using the factory.
*/ */
injectImpl: null|(<T>(token: Type<T>|InjectionToken<T>, flags?: InjectFlags) => T); injectImpl: null|
(<T>(token: Type<T>|AbstractType<T>|InjectionToken<T>, flags?: InjectFlags) => T);
/** /**
* Marker set to true during factory invocation to see if we get into recursive loop. * Marker set to true during factory invocation to see if we get into recursive loop.
@ -280,7 +281,7 @@ export class NodeInjectorFactory {
*/ */
isViewProvider: boolean, isViewProvider: boolean,
injectImplementation: null| injectImplementation: null|
(<T>(token: Type<T>|InjectionToken<T>, flags?: InjectFlags) => T)) { (<T>(token: Type<T>|AbstractType<T>|InjectionToken<T>, flags?: InjectFlags) => T)) {
ngDevMode && assertDefined(factory, 'Factory not specified'); ngDevMode && assertDefined(factory, 'Factory not specified');
ngDevMode && assertEqual(typeof factory, 'function', 'Expected factory function.'); ngDevMode && assertEqual(typeof factory, 'function', 'Expected factory function.');
this.canSeeViewProviders = isViewProvider; this.canSeeViewProviders = isViewProvider;

View File

@ -180,6 +180,15 @@ describe('InjectorDef-based createInjector()', () => {
class ChildService extends ServiceWithDep {} class ChildService extends ServiceWithDep {}
abstract class AbstractService {
static ɵprov = ɵɵdefineInjectable({
token: AbstractService,
providedIn: null,
factory: () => new AbstractServiceImpl(),
});
}
class AbstractServiceImpl extends AbstractService {}
class Module { class Module {
static ɵinj = ɵɵdefineInjector({ static ɵinj = ɵɵdefineInjector({
factory: () => new Module(), factory: () => new Module(),
@ -200,10 +209,17 @@ describe('InjectorDef-based createInjector()', () => {
CircularB, CircularB,
{provide: STATIC_TOKEN, useClass: StaticService, deps: [Service]}, {provide: STATIC_TOKEN, useClass: StaticService, deps: [Service]},
InjectorWithDep, InjectorWithDep,
AbstractService,
], ],
}); });
} }
const ABSTRACT_SERVICE_TOKEN_WITH_FACTORY =
new InjectionToken<AbstractService>('ABSTRACT_SERVICE_TOKEN', {
providedIn: Module,
factory: () => ɵɵinject(AbstractService),
});
class OtherModule { class OtherModule {
static ɵinj = ɵɵdefineInjector({ static ɵinj = ɵɵdefineInjector({
factory: () => new OtherModule(), factory: () => new OtherModule(),
@ -457,6 +473,18 @@ describe('InjectorDef-based createInjector()', () => {
expect(injector.get(ImportsNotAModule)).toBeDefined(); expect(injector.get(ImportsNotAModule)).toBeDefined();
}); });
it('injects an abstract class', () => {
const instance = injector.get(AbstractService);
expect(instance instanceof AbstractServiceImpl).toBeTruthy();
expect(injector.get(AbstractService)).toBe(instance);
});
it('injects an abstract class in an InjectionToken factory', () => {
const instance = injector.get(ABSTRACT_SERVICE_TOKEN_WITH_FACTORY);
expect(instance instanceof AbstractServiceImpl).toBeTruthy();
expect(injector.get(ABSTRACT_SERVICE_TOKEN_WITH_FACTORY)).toBe(instance);
});
describe('error handling', () => { describe('error handling', () => {
it('throws an error when a token is not found', () => { it('throws an error when a token is not found', () => {
expect(() => injector.get(ServiceTwo)).toThrow(); expect(() => injector.get(ServiceTwo)).toThrow();

View File

@ -6,13 +6,13 @@
* found in the LICENSE file at https://angular.io/license * found in the LICENSE file at https://angular.io/license
*/ */
import {inject, InjectFlags, InjectionToken, Injector, Type, ɵsetCurrentInjector as setCurrentInjector} from '@angular/core'; import {AbstractType, inject, InjectFlags, InjectionToken, Injector, Type, ɵsetCurrentInjector as setCurrentInjector} from '@angular/core';
class MockRootScopeInjector implements Injector { class MockRootScopeInjector implements Injector {
constructor(readonly parent: Injector) {} constructor(readonly parent: Injector) {}
get<T>( get<T>(
token: Type<T>|InjectionToken<T>, defaultValue?: any, token: Type<T>|AbstractType<T>|InjectionToken<T>, defaultValue?: any,
flags: InjectFlags = InjectFlags.Default): T { flags: InjectFlags = InjectFlags.Default): T {
if ((token as any).ɵprov && (token as any).ɵprov.providedIn === 'root') { if ((token as any).ɵprov && (token as any).ɵprov.providedIn === 'root') {
const old = setCurrentInjector(this); const old = setCurrentInjector(this);