Skip to content
This repository was archived by the owner on Jan 19, 2023. It is now read-only.

Commit a1b6ad0

Browse files
mitchellwillsAndrewKushnir
authored andcommitted
fix(core): Allow passing AbstractType to the inject function (angular#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 angular#37958
1 parent 453b32f commit a1b6ad0

File tree

11 files changed

+88
-48
lines changed

11 files changed

+88
-48
lines changed

goldens/public-api/core/core.d.ts

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -445,7 +445,7 @@ export declare class InjectionToken<T> {
445445
}
446446

447447
export declare abstract class Injector {
448-
abstract get<T>(token: Type<T> | InjectionToken<T> | AbstractType<T>, notFoundValue?: T, flags?: InjectFlags): T;
448+
abstract get<T>(token: Type<T> | AbstractType<T> | InjectionToken<T>, notFoundValue?: T, flags?: InjectFlags): T;
449449
/** @deprecated */ abstract get(token: any, notFoundValue?: any): any;
450450
static NULL: Injector;
451451
static THROW_IF_NOT_FOUND: {};
@@ -676,8 +676,8 @@ export declare function ɵɵdefineInjectable<T>(opts: {
676676
}): never;
677677

678678
/** @codeGenApi */
679-
export declare function ɵɵinject<T>(token: Type<T> | InjectionToken<T>): T;
680-
export declare function ɵɵinject<T>(token: Type<T> | InjectionToken<T>, flags?: InjectFlags): T | null;
679+
export declare function ɵɵinject<T>(token: Type<T> | AbstractType<T> | InjectionToken<T>): T;
680+
export declare function ɵɵinject<T>(token: Type<T> | AbstractType<T> | InjectionToken<T>, flags?: InjectFlags): T | null;
681681

682682
/** @codeGenApi */
683683
export declare interface ɵɵInjectableDef<T> {

packages/core/src/di/inject_switch.ts

Lines changed: 10 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -6,7 +6,7 @@
66
* found in the LICENSE file at https://angular.io/license
77
*/
88

9-
import {Type} from '../interface/type';
9+
import {AbstractType, Type} from '../interface/type';
1010
import {assertNotEqual} from '../util/assert';
1111
import {stringify} from '../util/stringify';
1212
import {InjectionToken} from './injection_token';
@@ -23,7 +23,8 @@ import {InjectFlags} from './interface/injector';
2323
* 1. `Injector` should not depend on ivy logic.
2424
* 2. To maintain tree shake-ability we don't want to bring in unnecessary code.
2525
*/
26-
let _injectImplementation: (<T>(token: Type<T>|InjectionToken<T>, flags?: InjectFlags) => T | null)|
26+
let _injectImplementation:
27+
(<T>(token: Type<T>|AbstractType<T>|InjectionToken<T>, flags?: InjectFlags) => T | null)|
2728
undefined;
2829
export function getInjectImplementation() {
2930
return _injectImplementation;
@@ -34,8 +35,10 @@ export function getInjectImplementation() {
3435
* Sets the current inject implementation.
3536
*/
3637
export function setInjectImplementation(
37-
impl: (<T>(token: Type<T>|InjectionToken<T>, flags?: InjectFlags) => T | null)|
38-
undefined): (<T>(token: Type<T>|InjectionToken<T>, flags?: InjectFlags) => T | null)|undefined {
38+
impl: (<T>(token: Type<T>|AbstractType<T>|InjectionToken<T>, flags?: InjectFlags) => T | null)|
39+
undefined):
40+
(<T>(token: Type<T>|AbstractType<T>|InjectionToken<T>, flags?: InjectFlags) => T | null)|
41+
undefined {
3942
const previous = _injectImplementation;
4043
_injectImplementation = impl;
4144
return previous;
@@ -50,7 +53,8 @@ export function setInjectImplementation(
5053
* `InjectableDef`.
5154
*/
5255
export function injectRootLimpMode<T>(
53-
token: Type<T>|InjectionToken<T>, notFoundValue: T|undefined, flags: InjectFlags): T|null {
56+
token: Type<T>|AbstractType<T>|InjectionToken<T>, notFoundValue: T|undefined,
57+
flags: InjectFlags): T|null {
5458
const injectableDef: ɵɵInjectableDef<T>|null = getInjectableDef(token);
5559
if (injectableDef && injectableDef.providedIn == 'root') {
5660
return injectableDef.value === undefined ? injectableDef.value = injectableDef.factory() :
@@ -70,7 +74,7 @@ export function injectRootLimpMode<T>(
7074
* @param fn Function which it should not equal to
7175
*/
7276
export function assertInjectImplementationNotEqual(
73-
fn: (<T>(token: Type<T>|InjectionToken<T>, flags?: InjectFlags) => T | null)) {
77+
fn: (<T>(token: Type<T>|AbstractType<T>|InjectionToken<T>, flags?: InjectFlags) => T | null)) {
7478
ngDevMode &&
7579
assertNotEqual(_injectImplementation, fn, 'Calling ɵɵinject would cause infinite recursion');
7680
}

packages/core/src/di/injector.ts

Lines changed: 4 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -67,9 +67,9 @@ export abstract class Injector {
6767
* @throws When the `notFoundValue` is `undefined` or `Injector.THROW_IF_NOT_FOUND`.
6868
*/
6969
abstract get<T>(
70-
token: Type<T>|InjectionToken<T>|AbstractType<T>, notFoundValue?: T, flags?: InjectFlags): T;
70+
token: Type<T>|AbstractType<T>|InjectionToken<T>, notFoundValue?: T, flags?: InjectFlags): T;
7171
/**
72-
* @deprecated from v4.0.0 use Type<T> or InjectionToken<T>
72+
* @deprecated from v4.0.0 use Type<T>, AbstractType<T> or InjectionToken<T>
7373
* @suppress {duplicate}
7474
*/
7575
abstract get(token: any, notFoundValue?: any): any;
@@ -156,7 +156,8 @@ export class StaticInjector implements Injector {
156156
this.scope = recursivelyProcessProviders(records, providers);
157157
}
158158

159-
get<T>(token: Type<T>|InjectionToken<T>, notFoundValue?: T, flags?: InjectFlags): T;
159+
get<T>(token: Type<T>|AbstractType<T>|InjectionToken<T>, notFoundValue?: T, flags?: InjectFlags):
160+
T;
160161
get(token: any, notFoundValue?: any): any;
161162
get(token: any, notFoundValue?: any, flags: InjectFlags = InjectFlags.Default): any {
162163
const records = this._records;

packages/core/src/di/injector_compatibility.ts

Lines changed: 10 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -8,7 +8,7 @@
88

99
import '../util/ng_dev_mode';
1010

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

49-
50-
export function injectInjectorOnly<T>(token: Type<T>|InjectionToken<T>): T;
51-
export function injectInjectorOnly<T>(token: Type<T>|InjectionToken<T>, flags?: InjectFlags): T|
52-
null;
49+
export function injectInjectorOnly<T>(token: Type<T>|AbstractType<T>|InjectionToken<T>): T;
50+
export function injectInjectorOnly<T>(
51+
token: Type<T>|AbstractType<T>|InjectionToken<T>, flags?: InjectFlags): T|null;
5352
export function injectInjectorOnly<T>(
54-
token: Type<T>|InjectionToken<T>, flags = InjectFlags.Default): T|null {
53+
token: Type<T>|AbstractType<T>|InjectionToken<T>, flags = InjectFlags.Default): T|null {
5554
if (_currentInjector === undefined) {
5655
throw new Error(`inject() must be called from an injection context`);
5756
} else if (_currentInjector === null) {
@@ -74,9 +73,11 @@ export function injectInjectorOnly<T>(
7473
* @codeGenApi
7574
* @publicApi This instruction has been emitted by ViewEngine for some time and is deployed to npm.
7675
*/
77-
export function ɵɵinject<T>(token: Type<T>|InjectionToken<T>): T;
78-
export function ɵɵinject<T>(token: Type<T>|InjectionToken<T>, flags?: InjectFlags): T|null;
79-
export function ɵɵinject<T>(token: Type<T>|InjectionToken<T>, flags = InjectFlags.Default): T|null {
76+
export function ɵɵinject<T>(token: Type<T>|AbstractType<T>|InjectionToken<T>): T;
77+
export function ɵɵinject<T>(
78+
token: Type<T>|AbstractType<T>|InjectionToken<T>, flags?: InjectFlags): T|null;
79+
export function ɵɵinject<T>(
80+
token: Type<T>|AbstractType<T>|InjectionToken<T>, flags = InjectFlags.Default): T|null {
8081
return (getInjectImplementation() || injectInjectorOnly)(resolveForwardRef(token), flags);
8182
}
8283

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

133-
134134
export function injectArgs(types: (Type<any>|InjectionToken<any>|any[])[]): any[] {
135135
const args: any[] = [];
136136
for (let i = 0; i < types.length; i++) {

packages/core/src/di/r3_injector.ts

Lines changed: 8 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -9,7 +9,7 @@
99
import '../util/ng_dev_mode';
1010

1111
import {OnDestroy} from '../interface/lifecycle_hooks';
12-
import {Type} from '../interface/type';
12+
import {AbstractType, Type} from '../interface/type';
1313
import {FactoryFn, getFactoryDef} from '../render3/definition_factory';
1414
import {throwCyclicDependencyError, throwInvalidProviderError, throwMixedMultiProviderError} from '../render3/errors_di';
1515
import {deepForEach, newArray} from '../util/array_utils';
@@ -103,7 +103,7 @@ export class R3Injector {
103103
* - `null` value implies that we don't have the record. Used by tree-shakable injectors
104104
* to prevent further searches.
105105
*/
106-
private records = new Map<Type<any>|InjectionToken<any>, Record<any>|null>();
106+
private records = new Map<Type<any>|AbstractType<any>|InjectionToken<any>, Record<any>|null>();
107107

108108
/**
109109
* The transitive set of `InjectorType`s which define this injector.
@@ -181,7 +181,7 @@ export class R3Injector {
181181
}
182182

183183
get<T>(
184-
token: Type<T>|InjectionToken<T>, notFoundValue: any = THROW_IF_NOT_FOUND,
184+
token: Type<T>|AbstractType<T>|InjectionToken<T>, notFoundValue: any = THROW_IF_NOT_FOUND,
185185
flags = InjectFlags.Default): T {
186186
this.assertNotDestroyed();
187187
// Set the injection context.
@@ -404,7 +404,7 @@ export class R3Injector {
404404
this.records.set(token, record);
405405
}
406406

407-
private hydrate<T>(token: Type<T>|InjectionToken<T>, record: Record<T>): T {
407+
private hydrate<T>(token: Type<T>|AbstractType<T>|InjectionToken<T>, record: Record<T>): T {
408408
if (ngDevMode && record.value === CIRCULAR) {
409409
throwCyclicDependencyError(stringify(token));
410410
} else if (record.value === NOT_YET) {
@@ -428,7 +428,8 @@ export class R3Injector {
428428
}
429429
}
430430

431-
function injectableDefOrInjectorDefFactory(token: Type<any>|InjectionToken<any>): FactoryFn<any> {
431+
function injectableDefOrInjectorDefFactory(token: Type<any>|AbstractType<any>|
432+
InjectionToken<any>): FactoryFn<any> {
432433
// Most tokens will have an injectable def directly on them, which specifies a factory directly.
433434
const injectableDef = getInjectableDef(token);
434435
const factory = injectableDef !== null ? injectableDef.factory : getFactoryDef(token);
@@ -564,7 +565,8 @@ function hasOnDestroy(value: any): value is OnDestroy {
564565
typeof (value as OnDestroy).ngOnDestroy === 'function';
565566
}
566567

567-
function couldBeInjectableType(value: any): value is Type<any>|InjectionToken<any> {
568+
function couldBeInjectableType(value: any): value is Type<any>|AbstractType<any>|
569+
InjectionToken<any> {
568570
return (typeof value === 'function') ||
569571
(typeof value === 'object' && value instanceof InjectionToken);
570572
}

packages/core/src/render3/component_ref.ts

Lines changed: 4 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -10,7 +10,7 @@ import {ChangeDetectorRef as ViewEngine_ChangeDetectorRef} from '../change_detec
1010
import {InjectionToken} from '../di/injection_token';
1111
import {Injector} from '../di/injector';
1212
import {InjectFlags} from '../di/interface/injector';
13-
import {Type} from '../interface/type';
13+
import {AbstractType, Type} from '../interface/type';
1414
import {ComponentFactory as viewEngine_ComponentFactory, ComponentRef as viewEngine_ComponentRef} from '../linker/component_factory';
1515
import {ComponentFactoryResolver as viewEngine_ComponentFactoryResolver} from '../linker/component_factory_resolver';
1616
import {createElementRef, ElementRef as viewEngine_ElementRef} from '../linker/element_ref';
@@ -80,7 +80,9 @@ export const SCHEDULER = new InjectionToken<((fn: () => void) => void)>('SCHEDUL
8080

8181
function createChainedInjector(rootViewInjector: Injector, moduleInjector: Injector): Injector {
8282
return {
83-
get: <T>(token: Type<T>|InjectionToken<T>, notFoundValue?: T, flags?: InjectFlags): T => {
83+
get: <T>(
84+
token: Type<T>|AbstractType<T>|InjectionToken<T>, notFoundValue?: T,
85+
flags?: InjectFlags): T => {
8486
const value = rootViewInjector.get(token, NOT_FOUND_CHECK_ONLY_ELEMENT_INJECTOR as T, flags);
8587

8688
if (value !== NOT_FOUND_CHECK_ONLY_ELEMENT_INJECTOR ||

packages/core/src/render3/di.ts

Lines changed: 10 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -13,7 +13,7 @@ import {Injector} from '../di/injector';
1313
import {InjectorMarkers} from '../di/injector_marker';
1414
import {getInjectorDef} from '../di/interface/defs';
1515
import {InjectFlags} from '../di/interface/injector';
16-
import {Type} from '../interface/type';
16+
import {AbstractType, Type} from '../interface/type';
1717
import {assertDefined, assertEqual, assertIndexInRange} from '../util/assert';
1818
import {noSideEffects} from '../util/closure';
1919

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

348348

349349
function notFoundValueOrThrow<T>(
350-
notFoundValue: T|null, token: Type<T>|InjectionToken<T>, flags: InjectFlags): T|null {
350+
notFoundValue: T|null, token: Type<T>|AbstractType<T>|InjectionToken<T>, flags: InjectFlags): T|
351+
null {
351352
if (flags & InjectFlags.Optional) {
352353
return notFoundValue;
353354
} else {
@@ -365,8 +366,8 @@ function notFoundValueOrThrow<T>(
365366
* @returns the value from the injector or throws an exception
366367
*/
367368
function lookupTokenUsingModuleInjector<T>(
368-
lView: LView, token: Type<T>|InjectionToken<T>, flags: InjectFlags, notFoundValue?: any): T|
369-
null {
369+
lView: LView, token: Type<T>|AbstractType<T>|InjectionToken<T>, flags: InjectFlags,
370+
notFoundValue?: any): T|null {
370371
if (flags & InjectFlags.Optional && notFoundValue === undefined) {
371372
// This must be set or the NullInjector will throw for optional deps
372373
notFoundValue = null;
@@ -408,7 +409,7 @@ function lookupTokenUsingModuleInjector<T>(
408409
* @returns the value from the injector, `null` when not found, or `notFoundValue` if provided
409410
*/
410411
export function getOrCreateInjectable<T>(
411-
tNode: TDirectiveHostNode|null, lView: LView, token: Type<T>|InjectionToken<T>,
412+
tNode: TDirectiveHostNode|null, lView: LView, token: Type<T>|AbstractType<T>|InjectionToken<T>,
412413
flags: InjectFlags = InjectFlags.Default, notFoundValue?: any): T|null {
413414
if (tNode !== null) {
414415
const bloomHash = bloomHashBitOrFactory(token);
@@ -508,7 +509,7 @@ export function createNodeInjector(): Injector {
508509
}
509510

510511
function searchTokensOnInjector<T>(
511-
injectorIndex: number, lView: LView, token: Type<T>|InjectionToken<T>,
512+
injectorIndex: number, lView: LView, token: Type<T>|AbstractType<T>|InjectionToken<T>,
512513
previousTView: TView|null, flags: InjectFlags, hostTElementNode: TNode|null) {
513514
const currentTView = lView[TVIEW];
514515
const tNode = currentTView.data[injectorIndex + NodeInjectorOffset.TNODE] as TNode;
@@ -555,7 +556,7 @@ function searchTokensOnInjector<T>(
555556
* @returns Index of a found directive or provider, or null when none found.
556557
*/
557558
export function locateDirectiveOrProvider<T>(
558-
tNode: TNode, tView: TView, token: Type<T>|InjectionToken<T>|string,
559+
tNode: TNode, tView: TView, token: Type<T>|AbstractType<T>|InjectionToken<T>|string,
559560
canAccessViewProviders: boolean, isHostSpecialCase: boolean|number): number|null {
560561
const nodeProviderIndexes = tNode.providerIndexes;
561562
const tInjectables = tView.data;
@@ -646,8 +647,8 @@ export function getNodeInjectable(
646647
* @returns the matching bit to check in the bloom filter or `null` if the token is not known.
647648
* When the returned value is negative then it represents special values such as `Injector`.
648649
*/
649-
export function bloomHashBitOrFactory(token: Type<any>|InjectionToken<any>|string): number|Function|
650-
undefined {
650+
export function bloomHashBitOrFactory(token: Type<any>|AbstractType<any>|InjectionToken<any>|
651+
string): number|Function|undefined {
651652
ngDevMode && assertDefined(token, 'token must be defined');
652653
if (typeof token === 'string') {
653654
return token.charCodeAt(0) || 0;

packages/core/src/render3/instructions/di.ts

Lines changed: 5 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -8,7 +8,7 @@
88
import {InjectFlags, InjectionToken, resolveForwardRef} from '../../di';
99
import {assertInjectImplementationNotEqual} from '../../di/inject_switch';
1010
import {ɵɵinject} from '../../di/injector_compatibility';
11-
import {Type} from '../../interface/type';
11+
import {AbstractType, Type} from '../../interface/type';
1212
import {getOrCreateInjectable} from '../di';
1313
import {TDirectiveHostNode} from '../interfaces/node';
1414
import {getCurrentTNode, getLView} from '../state';
@@ -37,10 +37,11 @@ import {getCurrentTNode, getLView} from '../state';
3737
*
3838
* @codeGenApi
3939
*/
40-
export function ɵɵdirectiveInject<T>(token: Type<T>|InjectionToken<T>): T;
41-
export function ɵɵdirectiveInject<T>(token: Type<T>|InjectionToken<T>, flags: InjectFlags): T;
40+
export function ɵɵdirectiveInject<T>(token: Type<T>|AbstractType<T>|InjectionToken<T>): T;
4241
export function ɵɵdirectiveInject<T>(
43-
token: Type<T>|InjectionToken<T>, flags = InjectFlags.Default): T|null {
42+
token: Type<T>|AbstractType<T>|InjectionToken<T>, flags: InjectFlags): T;
43+
export function ɵɵdirectiveInject<T>(
44+
token: Type<T>|AbstractType<T>|InjectionToken<T>, flags = InjectFlags.Default): T|null {
4445
const lView = getLView();
4546
// Fall back to inject() if view hasn't been created. This situation can happen in tests
4647
// if inject utilities are used before bootstrapping.

packages/core/src/render3/interfaces/injector.ts

Lines changed: 4 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -8,7 +8,7 @@
88

99
import {InjectionToken} from '../../di/injection_token';
1010
import {InjectFlags} from '../../di/interface/injector';
11-
import {Type} from '../../interface/type';
11+
import {AbstractType, Type} from '../../interface/type';
1212
import {assertDefined, assertEqual} from '../../util/assert';
1313

1414
import {TDirectiveHostNode} from './node';
@@ -176,7 +176,8 @@ export class NodeInjectorFactory {
176176
/**
177177
* The inject implementation to be activated when using the factory.
178178
*/
179-
injectImpl: null|(<T>(token: Type<T>|InjectionToken<T>, flags?: InjectFlags) => T);
179+
injectImpl: null|
180+
(<T>(token: Type<T>|AbstractType<T>|InjectionToken<T>, flags?: InjectFlags) => T);
180181

181182
/**
182183
* Marker set to true during factory invocation to see if we get into recursive loop.
@@ -280,7 +281,7 @@ export class NodeInjectorFactory {
280281
*/
281282
isViewProvider: boolean,
282283
injectImplementation: null|
283-
(<T>(token: Type<T>|InjectionToken<T>, flags?: InjectFlags) => T)) {
284+
(<T>(token: Type<T>|AbstractType<T>|InjectionToken<T>, flags?: InjectFlags) => T)) {
284285
ngDevMode && assertDefined(factory, 'Factory not specified');
285286
ngDevMode && assertEqual(typeof factory, 'function', 'Expected factory function.');
286287
this.canSeeViewProviders = isViewProvider;

packages/core/test/di/r3_injector_spec.ts

Lines changed: 28 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -180,6 +180,15 @@ describe('InjectorDef-based createInjector()', () => {
180180

181181
class ChildService extends ServiceWithDep {}
182182

183+
abstract class AbstractService {
184+
static ɵprov = ɵɵdefineInjectable({
185+
token: AbstractService,
186+
providedIn: null,
187+
factory: () => new AbstractServiceImpl(),
188+
});
189+
}
190+
class AbstractServiceImpl extends AbstractService {}
191+
183192
class Module {
184193
static ɵinj = ɵɵdefineInjector({
185194
factory: () => new Module(),
@@ -200,10 +209,17 @@ describe('InjectorDef-based createInjector()', () => {
200209
CircularB,
201210
{provide: STATIC_TOKEN, useClass: StaticService, deps: [Service]},
202211
InjectorWithDep,
212+
AbstractService,
203213
],
204214
});
205215
}
206216

217+
const ABSTRACT_SERVICE_TOKEN_WITH_FACTORY =
218+
new InjectionToken<AbstractService>('ABSTRACT_SERVICE_TOKEN', {
219+
providedIn: Module,
220+
factory: () => ɵɵinject(AbstractService),
221+
});
222+
207223
class OtherModule {
208224
static ɵinj = ɵɵdefineInjector({
209225
factory: () => new OtherModule(),
@@ -457,6 +473,18 @@ describe('InjectorDef-based createInjector()', () => {
457473
expect(injector.get(ImportsNotAModule)).toBeDefined();
458474
});
459475

476+
it('injects an abstract class', () => {
477+
const instance = injector.get(AbstractService);
478+
expect(instance instanceof AbstractServiceImpl).toBeTruthy();
479+
expect(injector.get(AbstractService)).toBe(instance);
480+
});
481+
482+
it('injects an abstract class in an InjectionToken factory', () => {
483+
const instance = injector.get(ABSTRACT_SERVICE_TOKEN_WITH_FACTORY);
484+
expect(instance instanceof AbstractServiceImpl).toBeTruthy();
485+
expect(injector.get(ABSTRACT_SERVICE_TOKEN_WITH_FACTORY)).toBe(instance);
486+
});
487+
460488
describe('error handling', () => {
461489
it('throws an error when a token is not found', () => {
462490
expect(() => injector.get(ServiceTwo)).toThrow();

0 commit comments

Comments
 (0)