Skip to content
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.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
6 changes: 3 additions & 3 deletions goldens/public-api/core/core.d.ts
Original file line number Diff line number Diff line change
Expand Up @@ -445,7 +445,7 @@ export declare class InjectionToken<T> {
}

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;
static NULL: Injector;
static THROW_IF_NOT_FOUND: {};
Expand Down Expand Up @@ -676,8 +676,8 @@ export declare function ɵɵdefineInjectable<T>(opts: {
}): never;

/** @codeGenApi */
export declare function ɵɵinject<T>(token: Type<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>): T;
export declare function ɵɵinject<T>(token: Type<T> | AbstractType<T> | InjectionToken<T>, flags?: InjectFlags): T | null;

/** @codeGenApi */
export declare interface ɵɵInjectableDef<T> {
Expand Down
16 changes: 10 additions & 6 deletions packages/core/src/di/inject_switch.ts
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,7 @@
* 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 {stringify} from '../util/stringify';
import {InjectionToken} from './injection_token';
Expand All @@ -23,7 +23,8 @@ import {InjectFlags} from './interface/injector';
* 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)|
let _injectImplementation:
(<T>(token: Type<T>|AbstractType<T>|InjectionToken<T>, flags?: InjectFlags) => T | null)|
undefined;
export function getInjectImplementation() {
return _injectImplementation;
Expand All @@ -34,8 +35,10 @@ export function getInjectImplementation() {
* 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 {
impl: (<T>(token: Type<T>|AbstractType<T>|InjectionToken<T>, flags?: InjectFlags) => T | null)|
undefined):
(<T>(token: Type<T>|AbstractType<T>|InjectionToken<T>, flags?: InjectFlags) => T | null)|
undefined {
const previous = _injectImplementation;
_injectImplementation = impl;
return previous;
Expand All @@ -50,7 +53,8 @@ export function setInjectImplementation(
* `InjectableDef`.
*/
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);
if (injectableDef && injectableDef.providedIn == 'root') {
return injectableDef.value === undefined ? injectableDef.value = injectableDef.factory() :
Expand All @@ -70,7 +74,7 @@ export function injectRootLimpMode<T>(
* @param fn Function which it should not equal to
*/
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 &&
assertNotEqual(_injectImplementation, fn, 'Calling ɵɵinject would cause infinite recursion');
}
7 changes: 4 additions & 3 deletions packages/core/src/di/injector.ts
Original file line number Diff line number Diff line change
Expand Up @@ -67,9 +67,9 @@ export abstract class Injector {
* @throws When the `notFoundValue` is `undefined` or `Injector.THROW_IF_NOT_FOUND`.
*/
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}
*/
abstract get(token: any, notFoundValue?: any): any;
Expand Down Expand Up @@ -156,7 +156,8 @@ export class StaticInjector implements Injector {
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, flags: InjectFlags = InjectFlags.Default): any {
const records = this._records;
Expand Down
20 changes: 10 additions & 10 deletions packages/core/src/di/injector_compatibility.ts
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,7 @@

import '../util/ng_dev_mode';

import {Type} from '../interface/type';
import {AbstractType, Type} from '../interface/type';
import {getClosureSafeProperty} from '../util/property';
import {stringify} from '../util/stringify';
import {resolveForwardRef} from './forward_ref';
Expand Down Expand Up @@ -46,12 +46,11 @@ export function setCurrentInjector(injector: Injector|null|undefined): Injector|
return former;
}


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>|AbstractType<T>|InjectionToken<T>): T;
export function injectInjectorOnly<T>(
token: Type<T>|AbstractType<T>|InjectionToken<T>, flags?: InjectFlags): T|null;
export function injectInjectorOnly<T>(
token: Type<T>|InjectionToken<T>, flags = InjectFlags.Default): T|null {
token: Type<T>|AbstractType<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) {
Expand All @@ -74,9 +73,11 @@ export function injectInjectorOnly<T>(
* @codeGenApi
* @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>|InjectionToken<T>, flags?: InjectFlags): T|null;
export function ɵɵinject<T>(token: Type<T>|InjectionToken<T>, flags = InjectFlags.Default): T|null {
export function ɵɵinject<T>(token: Type<T>|AbstractType<T>|InjectionToken<T>): T;
export function ɵɵinject<T>(
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);
}

Expand Down Expand Up @@ -130,7 +131,6 @@ Please check that 1) the type for the parameter at index ${
*/
export const inject = ɵɵinject;


export function injectArgs(types: (Type<any>|InjectionToken<any>|any[])[]): any[] {
const args: any[] = [];
for (let i = 0; i < types.length; i++) {
Expand Down
14 changes: 8 additions & 6 deletions packages/core/src/di/r3_injector.ts
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,7 @@
import '../util/ng_dev_mode';

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 {throwCyclicDependencyError, throwInvalidProviderError, throwMixedMultiProviderError} from '../render3/errors_di';
import {deepForEach, newArray} from '../util/array_utils';
Expand Down Expand Up @@ -103,7 +103,7 @@ export class R3Injector {
* - `null` value implies that we don't have the record. Used by tree-shakable injectors
* 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.
Expand Down Expand Up @@ -181,7 +181,7 @@ export class R3Injector {
}

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 {
this.assertNotDestroyed();
// Set the injection context.
Expand Down Expand Up @@ -404,7 +404,7 @@ export class R3Injector {
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) {
throwCyclicDependencyError(stringify(token));
} else if (record.value === NOT_YET) {
Expand All @@ -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.
const injectableDef = getInjectableDef(token);
const factory = injectableDef !== null ? injectableDef.factory : getFactoryDef(token);
Expand Down Expand Up @@ -564,7 +565,8 @@ function hasOnDestroy(value: any): value is OnDestroy {
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') ||
(typeof value === 'object' && value instanceof InjectionToken);
}
6 changes: 4 additions & 2 deletions packages/core/src/render3/component_ref.ts
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,7 @@ import {ChangeDetectorRef as ViewEngine_ChangeDetectorRef} from '../change_detec
import {InjectionToken} from '../di/injection_token';
import {Injector} from '../di/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 {ComponentFactoryResolver as viewEngine_ComponentFactoryResolver} from '../linker/component_factory_resolver';
import {createElementRef, ElementRef as viewEngine_ElementRef} from '../linker/element_ref';
Expand Down Expand Up @@ -80,7 +80,9 @@ export const SCHEDULER = new InjectionToken<((fn: () => void) => void)>('SCHEDUL

function createChainedInjector(rootViewInjector: Injector, moduleInjector: Injector): Injector {
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);

if (value !== NOT_FOUND_CHECK_ONLY_ELEMENT_INJECTOR ||
Expand Down
19 changes: 10 additions & 9 deletions packages/core/src/render3/di.ts
Original file line number Diff line number Diff line change
Expand Up @@ -13,7 +13,7 @@ import {Injector} from '../di/injector';
import {InjectorMarkers} from '../di/injector_marker';
import {getInjectorDef} from '../di/interface/defs';
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 {noSideEffects} from '../util/closure';

Expand Down Expand Up @@ -347,7 +347,8 @@ export function injectAttributeImpl(tNode: TNode, attrNameToInject: string): str


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) {
return notFoundValue;
} else {
Expand All @@ -365,8 +366,8 @@ function notFoundValueOrThrow<T>(
* @returns the value from the injector or throws an exception
*/
function lookupTokenUsingModuleInjector<T>(
lView: LView, token: Type<T>|InjectionToken<T>, flags: InjectFlags, notFoundValue?: any): T|
null {
lView: LView, token: Type<T>|AbstractType<T>|InjectionToken<T>, flags: InjectFlags,
notFoundValue?: any): T|null {
if (flags & InjectFlags.Optional && notFoundValue === undefined) {
// This must be set or the NullInjector will throw for optional deps
notFoundValue = null;
Expand Down Expand Up @@ -408,7 +409,7 @@ function lookupTokenUsingModuleInjector<T>(
* @returns the value from the injector, `null` when not found, or `notFoundValue` if provided
*/
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 {
if (tNode !== null) {
const bloomHash = bloomHashBitOrFactory(token);
Expand Down Expand Up @@ -508,7 +509,7 @@ export function createNodeInjector(): Injector {
}

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) {
const currentTView = lView[TVIEW];
const tNode = currentTView.data[injectorIndex + NodeInjectorOffset.TNODE] as TNode;
Expand Down Expand Up @@ -555,7 +556,7 @@ function searchTokensOnInjector<T>(
* @returns Index of a found directive or provider, or null when none found.
*/
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 {
const nodeProviderIndexes = tNode.providerIndexes;
const tInjectables = tView.data;
Expand Down Expand Up @@ -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.
* 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|
undefined {
export function bloomHashBitOrFactory(token: Type<any>|AbstractType<any>|InjectionToken<any>|
string): number|Function|undefined {
ngDevMode && assertDefined(token, 'token must be defined');
if (typeof token === 'string') {
return token.charCodeAt(0) || 0;
Expand Down
9 changes: 5 additions & 4 deletions packages/core/src/render3/instructions/di.ts
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,7 @@
import {InjectFlags, InjectionToken, resolveForwardRef} from '../../di';
import {assertInjectImplementationNotEqual} from '../../di/inject_switch';
import {ɵɵinject} from '../../di/injector_compatibility';
import {Type} from '../../interface/type';
import {AbstractType, Type} from '../../interface/type';
import {getOrCreateInjectable} from '../di';
import {TDirectiveHostNode} from '../interfaces/node';
import {getCurrentTNode, getLView} from '../state';
Expand Down Expand Up @@ -37,10 +37,11 @@ import {getCurrentTNode, getLView} from '../state';
*
* @codeGenApi
*/
export function ɵɵdirectiveInject<T>(token: Type<T>|InjectionToken<T>): T;
export function ɵɵdirectiveInject<T>(token: Type<T>|InjectionToken<T>, flags: InjectFlags): T;
export function ɵɵdirectiveInject<T>(token: Type<T>|AbstractType<T>|InjectionToken<T>): 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();
// Fall back to inject() if view hasn't been created. This situation can happen in tests
// if inject utilities are used before bootstrapping.
Expand Down
7 changes: 4 additions & 3 deletions packages/core/src/render3/interfaces/injector.ts
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,7 @@

import {InjectionToken} from '../../di/injection_token';
import {InjectFlags} from '../../di/interface/injector';
import {Type} from '../../interface/type';
import {AbstractType, Type} from '../../interface/type';
import {assertDefined, assertEqual} from '../../util/assert';

import {TDirectiveHostNode} from './node';
Expand Down Expand Up @@ -176,7 +176,8 @@ export class NodeInjectorFactory {
/**
* 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.
Expand Down Expand Up @@ -280,7 +281,7 @@ export class NodeInjectorFactory {
*/
isViewProvider: boolean,
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 && assertEqual(typeof factory, 'function', 'Expected factory function.');
this.canSeeViewProviders = isViewProvider;
Expand Down
28 changes: 28 additions & 0 deletions packages/core/test/di/r3_injector_spec.ts
Original file line number Diff line number Diff line change
Expand Up @@ -180,6 +180,15 @@ describe('InjectorDef-based createInjector()', () => {

class ChildService extends ServiceWithDep {}

abstract class AbstractService {
static ɵprov = ɵɵdefineInjectable({
token: AbstractService,
providedIn: null,
factory: () => new AbstractServiceImpl(),
});
}
class AbstractServiceImpl extends AbstractService {}

class Module {
static ɵinj = ɵɵdefineInjector({
factory: () => new Module(),
Expand All @@ -200,10 +209,17 @@ describe('InjectorDef-based createInjector()', () => {
CircularB,
{provide: STATIC_TOKEN, useClass: StaticService, deps: [Service]},
InjectorWithDep,
AbstractService,
],
});
}

const ABSTRACT_SERVICE_TOKEN_WITH_FACTORY =
new InjectionToken<AbstractService>('ABSTRACT_SERVICE_TOKEN', {
providedIn: Module,
factory: () => ɵɵinject(AbstractService),
});

class OtherModule {
static ɵinj = ɵɵdefineInjector({
factory: () => new OtherModule(),
Expand Down Expand Up @@ -457,6 +473,18 @@ describe('InjectorDef-based createInjector()', () => {
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', () => {
it('throws an error when a token is not found', () => {
expect(() => injector.get(ServiceTwo)).toThrow();
Expand Down
Loading