Skip to content

Commit da31db7

Browse files
mheveryIgorMinar
authored andcommitted
feat(ivy): support injection even if no injector present (angular#23345)
- Remove default injection value from `inject` / `directiveInject` since it is not possible to set using annotations. - Module `Injector` is stored on `LView` instead of `LInjector` data structure because it can change only at `LView` level. (More efficient) - Add `ngInjectableDef` to `IterableDiffers` so that existing tests can pass as well as enable `IterableDiffers` to be injectable without `Injector` PR Close angular#23345
1 parent 6f213a7 commit da31db7

File tree

21 files changed

+175
-105
lines changed

21 files changed

+175
-105
lines changed

Diff for: packages/compiler-cli/test/ngc_spec.ts

+2-2
Original file line numberDiff line numberDiff line change
@@ -2237,7 +2237,7 @@ describe('ngc transformer command-line', () => {
22372237
constructor(e: Existing|null) {}
22382238
}
22392239
`);
2240-
expect(source).toMatch(/ngInjectableDef.*return ..\(..\.inject\(Existing, null, 0\)/);
2240+
expect(source).toMatch(/ngInjectableDef.*return ..\(..\.inject\(Existing, 8\)/);
22412241
});
22422242

22432243
it('compiles a useFactory InjectableDef with skip-self dep', () => {
@@ -2257,7 +2257,7 @@ describe('ngc transformer command-line', () => {
22572257
constructor(e: Existing) {}
22582258
}
22592259
`);
2260-
expect(source).toMatch(/ngInjectableDef.*return ..\(..\.inject\(Existing, undefined, 4\)/);
2260+
expect(source).toMatch(/ngInjectableDef.*return ..\(..\.inject\(Existing, 4\)/);
22612261
});
22622262

22632263
it('compiles a service that depends on a token', () => {

Diff for: packages/compiler/src/injectable_compiler.ts

+3-4
Original file line numberDiff line numberDiff line change
@@ -38,15 +38,14 @@ export class InjectableCompiler {
3838
private depsArray(deps: any[], ctx: OutputContext): o.Expression[] {
3939
return deps.map(dep => {
4040
let token = dep;
41-
let defaultValue = undefined;
4241
let args = [token];
4342
let flags: InjectFlags = InjectFlags.Default;
4443
if (Array.isArray(dep)) {
4544
for (let i = 0; i < dep.length; i++) {
4645
const v = dep[i];
4746
if (v) {
4847
if (v.ngMetadataName === 'Optional') {
49-
defaultValue = null;
48+
flags |= InjectFlags.Optional;
5049
} else if (v.ngMetadataName === 'SkipSelf') {
5150
flags |= InjectFlags.SkipSelf;
5251
} else if (v.ngMetadataName === 'Self') {
@@ -69,8 +68,8 @@ export class InjectableCompiler {
6968
tokenExpr = ctx.importExpr(token);
7069
}
7170

72-
if (flags !== InjectFlags.Default || defaultValue !== undefined) {
73-
args = [tokenExpr, o.literal(defaultValue), o.literal(flags)];
71+
if (flags !== InjectFlags.Default) {
72+
args = [tokenExpr, o.literal(flags)];
7473
} else {
7574
args = [tokenExpr];
7675
}

Diff for: packages/compiler/src/render3/r3_view_compiler.ts

+1-1
Original file line numberDiff line numberDiff line change
@@ -948,7 +948,7 @@ export function createFactory(
948948
const flags = extractFlags(dependency);
949949
if (flags != InjectFlags.Default) {
950950
// Append flag information if other than default.
951-
directiveInjectArgs.push(o.literal(undefined), o.literal(flags));
951+
directiveInjectArgs.push(o.literal(flags));
952952
}
953953
args.push(o.importExpr(R3.directiveInject).callFn(directiveInjectArgs));
954954
}

Diff for: packages/compiler/test/render3/r3_view_compiler_di_spec.ts

+5-5
Original file line numberDiff line numberDiff line change
@@ -53,11 +53,11 @@ describe('compiler compliance: dependency injection', () => {
5353
return new MyComponent(
5454
$r3$.ɵinjectAttribute('name'),
5555
$r3$.ɵdirectiveInject(MyService),
56-
$r3$.ɵdirectiveInject(MyService, (undefined as any), 1),
57-
$r3$.ɵdirectiveInject(MyService, (undefined as any), 2),
58-
$r3$.ɵdirectiveInject(MyService, (undefined as any), 4),
59-
$r3$.ɵdirectiveInject(MyService, (undefined as any), 8),
60-
$r3$.ɵdirectiveInject(MyService, (undefined as any), 10)
56+
$r3$.ɵdirectiveInject(MyService, 1),
57+
$r3$.ɵdirectiveInject(MyService, 2),
58+
$r3$.ɵdirectiveInject(MyService, 4),
59+
$r3$.ɵdirectiveInject(MyService, 8),
60+
$r3$.ɵdirectiveInject(MyService, 10)
6161
);
6262
}`;
6363

Diff for: packages/core/src/change_detection/differs/iterable_differs.ts

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

9+
import {InjectableDef, defineInjectable} from '../../di/defs';
910
import {Optional, SkipSelf} from '../../di/metadata';
1011
import {StaticProvider} from '../../di/provider';
12+
import {DefaultIterableDifferFactory} from '../differs/default_iterable_differ';
1113

1214

1315
/**
@@ -135,6 +137,11 @@ export interface IterableDifferFactory {
135137
*
136138
*/
137139
export class IterableDiffers {
140+
static ngInjectableDef: InjectableDef<IterableDiffers> = defineInjectable({
141+
providedIn: 'root',
142+
factory: () => new IterableDiffers([new DefaultIterableDifferFactory()])
143+
});
144+
138145
/**
139146
* @deprecated v4.0.0 - Should be private
140147
*/

Diff for: packages/core/src/core_private_export.ts

+1-1
Original file line numberDiff line numberDiff line change
@@ -13,7 +13,7 @@ export {devModeEqual as ɵdevModeEqual} from './change_detection/change_detectio
1313
export {isListLikeIterable as ɵisListLikeIterable} from './change_detection/change_detection_util';
1414
export {ChangeDetectorStatus as ɵChangeDetectorStatus, isDefaultChangeDetectionStrategy as ɵisDefaultChangeDetectionStrategy} from './change_detection/constants';
1515
export {Console as ɵConsole} from './console';
16-
export {setCurrentInjector as ɵsetCurrentInjector} from './di/injector';
16+
export {inject as ɵinject, setCurrentInjector as ɵsetCurrentInjector} from './di/injector';
1717
export {APP_ROOT as ɵAPP_ROOT} from './di/scope';
1818
export {ComponentFactory as ɵComponentFactory} from './linker/component_factory';
1919
export {CodegenComponentFactoryResolver as ɵCodegenComponentFactoryResolver} from './linker/component_factory_resolver';

Diff for: packages/core/src/di/defs.ts

+20-2
Original file line numberDiff line numberDiff line change
@@ -25,8 +25,25 @@ import {ClassProvider, ClassSansProvider, ConstructorProvider, ConstructorSansPr
2525
* @experimental
2626
*/
2727
export interface InjectableDef<T> {
28+
/**
29+
* Specifies that the given type belongs to a particular injector:
30+
* - `InjectorType` such as `NgModule`,
31+
* - `'root'` the root injector
32+
* - `'any'` all injectors.
33+
* - `null`, does not belong to any injector. Must be explicitly listed in the injector
34+
* `providers`.
35+
*/
2836
providedIn: InjectorType<any>|'root'|'any'|null;
37+
38+
/**
39+
* Factory method to execute to create an instance of the injectable.
40+
*/
2941
factory: () => T;
42+
43+
/**
44+
* In a case of no explicit injector, a location where the instance of the injectable is stored.
45+
*/
46+
value: T|undefined;
3047
}
3148

3249
/**
@@ -101,12 +118,13 @@ export interface InjectorTypeWithProviders<T> {
101118
* @experimental
102119
*/
103120
export function defineInjectable<T>(opts: {
104-
providedIn?: Type<any>| 'root' | null,
121+
providedIn?: Type<any>| 'root' | 'any' | null,
105122
factory: () => T,
106123
}): InjectableDef<T> {
107124
return {
108-
providedIn: (opts.providedIn as InjectorType<any>| 'root' | null | undefined) || null,
125+
providedIn: opts.providedIn as any || null,
109126
factory: opts.factory,
127+
value: undefined,
110128
};
111129
}
112130

Diff for: packages/core/src/di/injector.ts

+23-16
Original file line numberDiff line numberDiff line change
@@ -428,9 +428,15 @@ export const enum InjectFlags {
428428
Optional = 1 << 3,
429429
}
430430

431-
let _currentInjector: Injector|null = null;
431+
/**
432+
* Current injector value used by `inject`.
433+
* - `undefined`: it is an error to call `inject`
434+
* - `null`: `inject` can be called but there is no injector (limp-mode).
435+
* - Injector instance: Use the injector for resolution.
436+
*/
437+
let _currentInjector: Injector|undefined|null = undefined;
432438

433-
export function setCurrentInjector(injector: Injector | null): Injector|null {
439+
export function setCurrentInjector(injector: Injector | null | undefined): Injector|undefined|null {
434440
const former = _currentInjector;
435441
_currentInjector = injector;
436442
return former;
@@ -450,19 +456,21 @@ export function setCurrentInjector(injector: Injector | null): Injector|null {
450456
*
451457
* @experimental
452458
*/
453-
export function inject<T>(
454-
token: Type<T>| InjectionToken<T>, notFoundValue?: undefined, flags?: InjectFlags): T;
455-
export function inject<T>(
456-
token: Type<T>| InjectionToken<T>, notFoundValue: T, flags?: InjectFlags): T;
457-
export function inject<T>(
458-
token: Type<T>| InjectionToken<T>, notFoundValue: null, flags?: InjectFlags): T|null;
459-
export function inject<T>(
460-
token: Type<T>| InjectionToken<T>, notFoundValue?: T | null, flags = InjectFlags.Default): T|
461-
null {
462-
if (_currentInjector === null) {
459+
export function inject<T>(token: Type<T>| InjectionToken<T>): T;
460+
export function inject<T>(token: Type<T>| InjectionToken<T>, flags?: InjectFlags): T|null;
461+
export function inject<T>(token: Type<T>| InjectionToken<T>, flags = InjectFlags.Default): T|null {
462+
if (_currentInjector === undefined) {
463463
throw new Error(`inject() must be called from an injection context`);
464+
} else if (_currentInjector === null) {
465+
const injectableDef: InjectableDef<T> = (token as any).ngInjectableDef;
466+
if (injectableDef && injectableDef.providedIn == 'root') {
467+
return injectableDef.value === undefined ? injectableDef.value = injectableDef.factory() :
468+
injectableDef.value;
469+
}
470+
throw new Error(`Injector: NOT_FOUND [${stringify(token)}]`);
471+
} else {
472+
return _currentInjector.get(token, flags & InjectFlags.Optional ? null : undefined, flags);
464473
}
465-
return _currentInjector.get(token, notFoundValue, flags);
466474
}
467475

468476
export function injectArgs(types: (Type<any>| InjectionToken<any>| any[])[]): any[] {
@@ -474,13 +482,12 @@ export function injectArgs(types: (Type<any>| InjectionToken<any>| any[])[]): an
474482
throw new Error('Arguments array must have arguments.');
475483
}
476484
let type: Type<any>|undefined = undefined;
477-
let defaultValue: null|undefined = undefined;
478485
let flags: InjectFlags = InjectFlags.Default;
479486

480487
for (let j = 0; j < arg.length; j++) {
481488
const meta = arg[j];
482489
if (meta instanceof Optional || meta.__proto__.ngMetadataName === 'Optional') {
483-
defaultValue = null;
490+
flags |= InjectFlags.Optional;
484491
} else if (meta instanceof SkipSelf || meta.__proto__.ngMetadataName === 'SkipSelf') {
485492
flags |= InjectFlags.SkipSelf;
486493
} else if (meta instanceof Self || meta.__proto__.ngMetadataName === 'Self') {
@@ -492,7 +499,7 @@ export function injectArgs(types: (Type<any>| InjectionToken<any>| any[])[]): an
492499
}
493500
}
494501

495-
args.push(inject(type !, defaultValue, InjectFlags.Default));
502+
args.push(inject(type !, flags));
496503
} else {
497504
args.push(inject(arg));
498505
}

Diff for: packages/core/src/render3/component.ts

+2-1
Original file line numberDiff line numberDiff line change
@@ -132,10 +132,11 @@ export function renderComponent<T>(
132132
scheduler: opts.scheduler || requestAnimationFrame.bind(window),
133133
clean: CLEAN_PROMISE,
134134
};
135-
const rootView = createLView(
135+
const rootView: LView = createLView(
136136
-1, rendererFactory.createRenderer(hostNode, componentDef.rendererType),
137137
createTView(null, null), null, rootContext,
138138
componentDef.onPush ? LViewFlags.Dirty : LViewFlags.CheckAlways);
139+
rootView.injector = opts.injector || null;
139140

140141
const oldView = enterView(rootView, null !);
141142
let elementNode: LElementNode;

Diff for: packages/core/src/render3/di.ts

+14-30
Original file line numberDiff line numberDiff line change
@@ -9,7 +9,7 @@
99
// We are temporarily importing the existing viewEngine_from core so we can be sure we are
1010
// correctly implementing its interfaces for backwards compatibility.
1111
import {ChangeDetectorRef as viewEngine_ChangeDetectorRef} from '../change_detection/change_detector_ref';
12-
import {InjectFlags, Injector} from '../di/injector';
12+
import {InjectFlags, Injector, inject, setCurrentInjector} from '../di/injector';
1313
import {ComponentFactory as viewEngine_ComponentFactory, ComponentRef as viewEngine_ComponentRef} from '../linker/component_factory';
1414
import {ElementRef as viewEngine_ElementRef} from '../linker/element_ref';
1515
import {NgModuleRef as viewEngine_NgModuleRef} from '../linker/ng_module_factory';
@@ -125,24 +125,13 @@ export function getOrCreateNodeInjectorForNode(node: LElementNode | LContainerNo
125125
cbf5: parentInjector == null ? 0 : parentInjector.cbf5 | parentInjector.bf5,
126126
cbf6: parentInjector == null ? 0 : parentInjector.cbf6 | parentInjector.bf6,
127127
cbf7: parentInjector == null ? 0 : parentInjector.cbf7 | parentInjector.bf7,
128-
injector: null,
129128
templateRef: null,
130129
viewContainerRef: null,
131130
elementRef: null,
132131
changeDetectorRef: null
133132
};
134133
}
135134

136-
/**
137-
* Constructs an injection error with the given text and token.
138-
*
139-
* @param text The text of the error
140-
* @param token The token associated with the error
141-
* @returns The error that was created
142-
*/
143-
function createInjectionError(text: string, token: any) {
144-
return new Error(`ElementInjector: ${text} [${stringify(token)}]`);
145-
}
146135

147136
/**
148137
* Makes a directive public to the DI system by adding it to an injector's bloom filter.
@@ -188,14 +177,10 @@ export function diPublic(def: DirectiveDef<any>): void {
188177
* @param flags Injection flags (e.g. CheckParent)
189178
* @returns The instance found
190179
*/
191-
export function directiveInject<T>(
192-
token: Type<T>, notFoundValue?: undefined, flags?: InjectFlags): T;
193-
export function directiveInject<T>(token: Type<T>, notFoundValue: T, flags?: InjectFlags): T;
194-
export function directiveInject<T>(token: Type<T>, notFoundValue: null, flags?: InjectFlags): T|
195-
null;
196-
export function directiveInject<T>(
197-
token: Type<T>, notFoundValue?: T | null, flags = InjectFlags.Default): T|null {
198-
return getOrCreateInjectable<T>(getOrCreateNodeInjector(), token, flags, notFoundValue);
180+
export function directiveInject<T>(token: Type<T>): T;
181+
export function directiveInject<T>(token: Type<T>, flags?: InjectFlags): T|null;
182+
export function directiveInject<T>(token: Type<T>, flags = InjectFlags.Default): T|null {
183+
return getOrCreateInjectable<T>(getOrCreateNodeInjector(), token, flags);
199184
}
200185

201186
/**
@@ -344,21 +329,20 @@ function getClosestComponentAncestor(node: LViewNode | LElementNode): LElementNo
344329
* @param flags Injection flags (e.g. CheckParent)
345330
* @returns The instance found
346331
*/
347-
export function getOrCreateInjectable<T>(
348-
di: LInjector, token: Type<T>, flags?: InjectFlags, defaultValue?: T | null): T|null {
332+
export function getOrCreateInjectable<T>(di: LInjector, token: Type<T>, flags?: InjectFlags): T|
333+
null {
349334
const bloomHash = bloomHashBit(token);
350335

351336
// If the token has a bloom hash, then it is a directive that is public to the injection system
352337
// (diPublic). If there is no hash, fall back to the module injector.
353338
if (bloomHash === null) {
354-
const moduleInjector = di.injector;
355-
if (!moduleInjector) {
356-
if (defaultValue != null) {
357-
return defaultValue;
358-
}
359-
throw createInjectionError('NotFound', token);
339+
const moduleInjector = getPreviousOrParentNode().view.injector;
340+
const formerInjector = setCurrentInjector(moduleInjector);
341+
try {
342+
return inject(token, flags);
343+
} finally {
344+
setCurrentInjector(formerInjector);
360345
}
361-
moduleInjector.get(token);
362346
} else {
363347
let injector: LInjector|null = di;
364348

@@ -409,7 +393,7 @@ export function getOrCreateInjectable<T>(
409393

410394
// No directive was found for the given token.
411395
// TODO: implement optional, check-self, and check-parent.
412-
throw createInjectionError('Not found', token);
396+
throw new Error('Implement');
413397
}
414398

415399
function searchMatchesQueuedForCreation<T>(node: LNode, token: any): T|null {

Diff for: packages/core/src/render3/instructions.ts

+1
Original file line numberDiff line numberDiff line change
@@ -301,6 +301,7 @@ export function createLView<T>(
301301
dynamicViewCount: 0,
302302
lifecycleStage: LifecycleStage.Init,
303303
queries: null,
304+
injector: currentView && currentView.injector,
304305
};
305306

306307
return newView;

Diff for: packages/core/src/render3/interfaces/injector.ts

-3
Original file line numberDiff line numberDiff line change
@@ -7,7 +7,6 @@
77
*/
88

99
import {ChangeDetectorRef} from '../../change_detection/change_detector_ref';
10-
import {Injector} from '../../di/injector';
1110
import {ElementRef} from '../../linker/element_ref';
1211
import {TemplateRef} from '../../linker/template_ref';
1312
import {ViewContainerRef} from '../../linker/view_container_ref';
@@ -69,8 +68,6 @@ export interface LInjector {
6968
cbf6: number;
7069
cbf7: number;
7170

72-
injector: Injector|null;
73-
7471
/** Stores the TemplateRef so subsequent injections of the TemplateRef get the same instance. */
7572
templateRef: TemplateRef<any>|null;
7673

Diff for: packages/core/src/render3/interfaces/view.ts

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

9+
import {Injector} from '../../di/injector';
910
import {LContainer} from './container';
1011
import {ComponentTemplate, DirectiveDef, DirectiveDefList, PipeDef, PipeDefList} from './definition';
1112
import {LElementNode, LViewNode, TNode} from './node';
@@ -189,6 +190,11 @@ export interface LView {
189190
* Queries active for this view - nodes from a view are reported to those queries
190191
*/
191192
queries: LQueries|null;
193+
194+
/**
195+
* An optional Module Injector to be used as fall back after Element Injectors are consulted.
196+
*/
197+
injector: Injector|null;
192198
}
193199

194200
/** Flags associated with an LView (saved in LView.flags) */

Diff for: packages/core/test/render3/common_with_def.ts

+1-2
Original file line numberDiff line numberDiff line change
@@ -19,8 +19,7 @@ NgForOf.ngDirectiveDef = defineDirective({
1919
type: NgForOfDef,
2020
selectors: [['', 'ngForOf', '']],
2121
factory: () => new NgForOfDef(
22-
injectViewContainerRef(), injectTemplateRef(),
23-
directiveInject(IterableDiffers, defaultIterableDiffers, InjectFlags.Default)),
22+
injectViewContainerRef(), injectTemplateRef(), directiveInject(IterableDiffers)),
2423
features: [NgOnChangesFeature()],
2524
inputs: {
2625
ngForOf: 'ngForOf',

Diff for: packages/core/test/render3/compiler_canonical/injection_spec.ts

+1-1
Original file line numberDiff line numberDiff line change
@@ -185,7 +185,7 @@ describe('injection', () => {
185185
// NORMATIVE
186186
static ngInjectableDef = defineInjectable({
187187
factory: function ServiceA_Factory() {
188-
return new ServiceB(inject(ServiceA), inject(INJECTOR, undefined, InjectFlags.SkipSelf));
188+
return new ServiceB(inject(ServiceA), inject(INJECTOR, InjectFlags.SkipSelf) !);
189189
},
190190
});
191191
// /NORMATIVE

Diff for: packages/core/test/render3/compiler_canonical/ng_module_spec.ts

+1-1
Original file line numberDiff line numberDiff line change
@@ -71,7 +71,7 @@ xdescribe('NgModule', () => {
7171
static ngInjectableDef = defineInjectable({
7272
providedIn: MyModule,
7373
factory: () => new BurntToast(
74-
$r3$.ɵdirectiveInject(Toast, undefined, $core$.InjectFlags.Optional),
74+
$r3$.ɵdirectiveInject(Toast, $core$.InjectFlags.Optional),
7575
$r3$.ɵdirectiveInject(String)),
7676
});
7777
// /NORMATIVE

0 commit comments

Comments
 (0)