fix(ivy): walk the node injector tree and then the module injector tree (#25166)
- `directiveInjector()` is used to inject anything in the directive / component / pipe factories so adding `InjectionToken<T>` as a supported token type. - `getOrCreateInjectable()` should search first in the node injector tree and then in the module injector tree (was either or before the PR). PR Close #25166
This commit is contained in:
parent
d3c0915598
commit
15df853622
|
@ -7,28 +7,25 @@
|
|||
*/
|
||||
|
||||
import {StaticSymbol} from '../../aot/static_symbol';
|
||||
import {CompileDiDependencyMetadata, CompileDirectiveMetadata, CompileDirectiveSummary, CompilePipeMetadata, CompileQueryMetadata, CompileTokenMetadata, CompileTypeMetadata, flatten, identifierName, sanitizeIdentifier, tokenReference} from '../../compile_metadata';
|
||||
import {CompileDirectiveMetadata, CompileDirectiveSummary, CompileQueryMetadata, CompileTokenMetadata, identifierName, sanitizeIdentifier} from '../../compile_metadata';
|
||||
import {CompileReflector} from '../../compile_reflector';
|
||||
import {BindingForm, BuiltinFunctionCall, LocalResolver, convertActionBinding, convertPropertyBinding} from '../../compiler_util/expression_converter';
|
||||
import {BindingForm, convertActionBinding, convertPropertyBinding} from '../../compiler_util/expression_converter';
|
||||
import {ConstantPool, DefinitionKind} from '../../constant_pool';
|
||||
import * as core from '../../core';
|
||||
import {AST, AstMemoryEfficientTransformer, BindingPipe, BindingType, FunctionCall, ImplicitReceiver, LiteralArray, LiteralMap, LiteralPrimitive, PropertyRead} from '../../expression_parser/ast';
|
||||
import {Identifiers} from '../../identifiers';
|
||||
import {LifecycleHooks} from '../../lifecycle_reflector';
|
||||
import * as o from '../../output/output_ast';
|
||||
import {ParseSourceSpan, typeSourceSpan} from '../../parse_util';
|
||||
import {typeSourceSpan} from '../../parse_util';
|
||||
import {CssSelector, SelectorMatcher} from '../../selector';
|
||||
import {BindingParser} from '../../template_parser/binding_parser';
|
||||
import {OutputContext, error} from '../../util';
|
||||
import * as t from '../r3_ast';
|
||||
import {R3DependencyMetadata, R3ResolvedDependencyType, compileFactoryFunction, dependenciesFromGlobalMetadata} from '../r3_factory';
|
||||
import {compileFactoryFunction, dependenciesFromGlobalMetadata} from '../r3_factory';
|
||||
import {Identifiers as R3} from '../r3_identifiers';
|
||||
import {Render3ParseResult} from '../r3_template_transform';
|
||||
import {typeWithParameters} from '../util';
|
||||
|
||||
import {R3ComponentDef, R3ComponentMetadata, R3DirectiveDef, R3DirectiveMetadata, R3QueryMetadata} from './api';
|
||||
import {BindingScope, TemplateDefinitionBuilder, renderFlagCheckIfStmt} from './template';
|
||||
import {CONTEXT_NAME, DefinitionMap, ID_SEPARATOR, MEANING_SEPARATOR, RENDER_FLAGS, TEMPORARY_NAME, asLiteral, conditionallyCreateMapObjectLiteral, getQueryPredicate, temporaryAllocator, unsupported} from './util';
|
||||
import {CONTEXT_NAME, DefinitionMap, RENDER_FLAGS, TEMPORARY_NAME, asLiteral, conditionallyCreateMapObjectLiteral, getQueryPredicate, temporaryAllocator} from './util';
|
||||
|
||||
function baseDirectiveFields(
|
||||
meta: R3DirectiveMetadata, constantPool: ConstantPool,
|
||||
|
|
|
@ -8,7 +8,7 @@
|
|||
|
||||
import {Type} from '../type';
|
||||
|
||||
import {InjectableDef, defineInjectable} from './defs';
|
||||
import {defineInjectable} from './defs';
|
||||
|
||||
/**
|
||||
* Creates a token that can be used in a DI Provider.
|
||||
|
|
|
@ -8,7 +8,9 @@
|
|||
|
||||
// We are temporarily importing the existing viewEngine_from core so we can be sure we are
|
||||
// correctly implementing its interfaces for backwards compatibility.
|
||||
|
||||
import {ChangeDetectorRef as viewEngine_ChangeDetectorRef} from '../change_detection/change_detector_ref';
|
||||
import {InjectionToken} from '../di/injection_token';
|
||||
import {InjectFlags, Injector, inject, setCurrentInjector} from '../di/injector';
|
||||
import {ComponentFactory as viewEngine_ComponentFactory, ComponentRef as viewEngine_ComponentRef} from '../linker/component_factory';
|
||||
import {ComponentFactoryResolver as viewEngine_ComponentFactoryResolver} from '../linker/component_factory_resolver';
|
||||
|
@ -31,7 +33,6 @@ import {Renderer3} from './interfaces/renderer';
|
|||
import {DIRECTIVES, HOST_NODE, INJECTOR, LViewData, QUERIES, RENDERER, TVIEW, TView} from './interfaces/view';
|
||||
import {assertNodeOfPossibleTypes, assertNodeType} from './node_assert';
|
||||
import {addRemoveViewFromContainer, appendChild, detachView, getChildLNode, getParentLNode, insertView, removeView} from './node_manipulation';
|
||||
import {stringify} from './util';
|
||||
import {ViewRef} from './view_ref';
|
||||
|
||||
|
||||
|
@ -61,7 +62,7 @@ let nextNgElementId = 0;
|
|||
* @param type The directive to register
|
||||
*/
|
||||
export function bloomAdd(injector: LInjector, type: Type<any>): void {
|
||||
let id: number|undefined = (type as any)[NG_ELEMENT_ID];
|
||||
let id = (type as any)[NG_ELEMENT_ID];
|
||||
|
||||
// Set a unique ID on the directive type, so if something tries to inject the directive,
|
||||
// we can easily retrieve the ID and hash it into the bloom bit that should be checked.
|
||||
|
@ -177,10 +178,10 @@ export function diPublic(def: DirectiveDefInternal<any>): void {
|
|||
* @param flags Injection flags
|
||||
* @returns the value from the injector or `null` when not found
|
||||
*/
|
||||
export function directiveInject<T>(token: Type<T>): T;
|
||||
export function directiveInject<T>(token: Type<T>, flags: InjectFlags.Optional): T|null;
|
||||
export function directiveInject<T>(token: Type<T>, flags: InjectFlags): T;
|
||||
export function directiveInject<T>(token: Type<T>, flags = InjectFlags.Default): T|null {
|
||||
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>| InjectionToken<T>, flags = InjectFlags.Default): T|null {
|
||||
return getOrCreateInjectable<T>(getOrCreateNodeInjector(), token, flags);
|
||||
}
|
||||
|
||||
|
@ -339,21 +340,14 @@ function getClosestComponentAncestor(node: LViewNode | LElementNode): LElementNo
|
|||
* @returns the value from the injector or `null` when not found
|
||||
*/
|
||||
export function getOrCreateInjectable<T>(
|
||||
di: LInjector, token: Type<T>, flags: InjectFlags = InjectFlags.Default): T|null {
|
||||
nodeInjector: LInjector, token: Type<T>| InjectionToken<T>,
|
||||
flags: InjectFlags = InjectFlags.Default): T|null {
|
||||
const bloomHash = bloomHashBit(token);
|
||||
|
||||
// If the token has a bloom hash, then it is a directive that is public to the injection system
|
||||
// (diPublic). If there is no hash, fall back to the module injector.
|
||||
if (bloomHash === null) {
|
||||
const moduleInjector = getPreviousOrParentNode().view[INJECTOR];
|
||||
const formerInjector = setCurrentInjector(moduleInjector);
|
||||
try {
|
||||
return inject(token, flags);
|
||||
} finally {
|
||||
setCurrentInjector(formerInjector);
|
||||
}
|
||||
} else {
|
||||
let injector: LInjector|null = di;
|
||||
// (diPublic) otherwise fall back to the module injector.
|
||||
if (bloomHash !== null) {
|
||||
let injector: LInjector|null = nodeInjector;
|
||||
|
||||
while (injector) {
|
||||
// Get the closest potential matching injector (upwards in the injector tree) that
|
||||
|
@ -390,7 +384,8 @@ export function getOrCreateInjectable<T>(
|
|||
// If we *didn't* find the directive for the token and we are searching the current node's
|
||||
// injector, it's possible the directive is on this node and hasn't been created yet.
|
||||
let instance: T|null;
|
||||
if (injector === di && (instance = searchMatchesQueuedForCreation<T>(node, token))) {
|
||||
if (injector === nodeInjector &&
|
||||
(instance = searchMatchesQueuedForCreation<T>(node, token))) {
|
||||
return instance;
|
||||
}
|
||||
|
||||
|
@ -404,9 +399,13 @@ export function getOrCreateInjectable<T>(
|
|||
}
|
||||
}
|
||||
|
||||
// No directive was found for the given token.
|
||||
if (flags & InjectFlags.Optional) return null;
|
||||
throw new Error(`Injector: NOT_FOUND [${stringify(token)}]`);
|
||||
const moduleInjector = getPreviousOrParentNode().view[INJECTOR];
|
||||
const formerInjector = setCurrentInjector(moduleInjector);
|
||||
try {
|
||||
return inject(token, flags);
|
||||
} finally {
|
||||
setCurrentInjector(formerInjector);
|
||||
}
|
||||
}
|
||||
|
||||
function searchMatchesQueuedForCreation<T>(node: LNode, token: any): T|null {
|
||||
|
@ -433,8 +432,8 @@ function searchMatchesQueuedForCreation<T>(node: LNode, token: any): T|null {
|
|||
* @param token the injection token
|
||||
* @returns the matching bit to check in the bloom filter or `null` if the token is not known.
|
||||
*/
|
||||
function bloomHashBit(type: Type<any>): number|null {
|
||||
let id: number|undefined = (type as any)[NG_ELEMENT_ID];
|
||||
function bloomHashBit(token: Type<any>| InjectionToken<any>): number|null {
|
||||
let id: number|undefined = (token as any)[NG_ELEMENT_ID];
|
||||
return typeof id === 'number' ? id % BLOOM_SIZE : null;
|
||||
}
|
||||
|
||||
|
@ -694,7 +693,7 @@ class ViewContainerRef implements viewEngine_ViewContainerRef {
|
|||
|
||||
detach(index?: number): viewEngine_ViewRef|null {
|
||||
const adjustedIdx = this._adjustIndex(index, -1);
|
||||
const lViewNode = detachView(this._lContainerNode, adjustedIdx);
|
||||
detachView(this._lContainerNode, adjustedIdx);
|
||||
return this._viewRefs.splice(adjustedIdx, 1)[0] || null;
|
||||
}
|
||||
|
||||
|
|
|
@ -6,7 +6,7 @@
|
|||
* found in the LICENSE file at https://angular.io/license
|
||||
*/
|
||||
|
||||
import {Expression, WrappedNodeExpr, compilePipeFromMetadata, jitExpression} from '@angular/compiler';
|
||||
import {WrappedNodeExpr, compilePipeFromMetadata, jitExpression} from '@angular/compiler';
|
||||
|
||||
import {Pipe} from '../../metadata/directives';
|
||||
import {Type} from '../../type';
|
||||
|
|
|
@ -117,7 +117,7 @@ describe('di', () => {
|
|||
.toEqual('<div dira=""><span dirb="" dirc="">DirADirB</span></div>');
|
||||
});
|
||||
|
||||
it('should instantiate injected directives first', () => {
|
||||
it('should instantiate injected directives in dependency order', () => {
|
||||
class DirA {
|
||||
constructor(dir: DirB) { log.push(`DirA (dep: ${dir.value})`); }
|
||||
|
||||
|
@ -136,10 +136,45 @@ describe('di', () => {
|
|||
}
|
||||
}, [DirA, DirB]);
|
||||
|
||||
const fixture = new ComponentFixture(App);
|
||||
new ComponentFixture(App);
|
||||
expect(log).toEqual(['DirB', 'DirA (dep: DirB)']);
|
||||
});
|
||||
|
||||
it('should fallback to the module injector', () => {
|
||||
class DirA {
|
||||
constructor(dir: DirB) { log.push(`DirA (dep: ${dir.value})`); }
|
||||
|
||||
static ngDirectiveDef = defineDirective({
|
||||
selectors: [['', 'dirA', '']],
|
||||
type: DirA,
|
||||
factory: () => new DirA(directiveInject(DirB)),
|
||||
});
|
||||
}
|
||||
|
||||
// `<div dirB></div><div dirA></div>`
|
||||
// - dirB is know to the node injectors (it uses the diPublic feature)
|
||||
// - then when dirA tries to inject dirB, it will check the node injector first tree
|
||||
// - if not found, it will check the module injector tree
|
||||
const App = createComponent('app', function(rf: RenderFlags, ctx: any) {
|
||||
if (rf & RenderFlags.Create) {
|
||||
elementStart(0, 'div', ['dirB', '']);
|
||||
elementEnd();
|
||||
elementStart(1, 'div', ['dirA', '']);
|
||||
elementEnd();
|
||||
}
|
||||
}, [DirA, DirB]);
|
||||
|
||||
const fakeModuleInjector: any = {
|
||||
get: function(token: any) {
|
||||
const value = token === DirB ? 'module' : 'fail';
|
||||
return {value: value};
|
||||
}
|
||||
};
|
||||
|
||||
new ComponentFixture(App, {injector: fakeModuleInjector});
|
||||
expect(log).toEqual(['DirB', 'DirA (dep: module)']);
|
||||
});
|
||||
|
||||
it('should instantiate injected directives before components', () => {
|
||||
class Comp {
|
||||
constructor(dir: DirB) { log.push(`Comp (dep: ${dir.value})`); }
|
||||
|
@ -160,7 +195,7 @@ describe('di', () => {
|
|||
}
|
||||
}, [Comp, DirB]);
|
||||
|
||||
const fixture = new ComponentFixture(App);
|
||||
new ComponentFixture(App);
|
||||
expect(log).toEqual(['DirB', 'Comp (dep: DirB)']);
|
||||
});
|
||||
|
||||
|
@ -197,7 +232,7 @@ describe('di', () => {
|
|||
containerRefreshEnd();
|
||||
}, [DirA, DirB]);
|
||||
|
||||
const fixture = new ComponentFixture(App);
|
||||
new ComponentFixture(App);
|
||||
expect(log).toEqual(
|
||||
['DirB', 'DirA (dep: DirB)', 'DirB', 'DirA (dep: DirB)', 'DirB', 'DirA (dep: DirB)']);
|
||||
});
|
||||
|
@ -247,7 +282,7 @@ describe('di', () => {
|
|||
}
|
||||
}, [DirA, DirB, DirC]);
|
||||
|
||||
const fixture = new ComponentFixture(App);
|
||||
new ComponentFixture(App);
|
||||
expect(log).toEqual(['DirA', 'DirC', 'DirB (deps: DirA and DirC)']);
|
||||
});
|
||||
|
||||
|
@ -307,7 +342,7 @@ describe('di', () => {
|
|||
}
|
||||
}, [Comp, DirA, DirB, DirC, DirD]);
|
||||
|
||||
const fixture = new ComponentFixture(App);
|
||||
new ComponentFixture(App);
|
||||
expect(log).toEqual(
|
||||
['DirB', 'DirC (dep: DirB)', 'DirA (dep: DirC)', 'DirD (dep: DirA)', 'Comp (dep: DirD)']);
|
||||
});
|
||||
|
@ -344,7 +379,7 @@ describe('di', () => {
|
|||
});
|
||||
}
|
||||
|
||||
const fixture = new ComponentFixture(App);
|
||||
new ComponentFixture(App);
|
||||
expect(log).toEqual(['DirB', 'DirA (deps: DirB and App)']);
|
||||
});
|
||||
|
||||
|
@ -393,7 +428,7 @@ describe('di', () => {
|
|||
}
|
||||
}, [Parent, DirB]);
|
||||
|
||||
const fixture = new ComponentFixture(App);
|
||||
new ComponentFixture(App);
|
||||
expect(log).toEqual(['DirB', 'DirB', 'DirA (dep: DirB - 2)']);
|
||||
});
|
||||
|
||||
|
@ -590,7 +625,7 @@ describe('di', () => {
|
|||
}, [DirA, DirB]);
|
||||
|
||||
expect(() => {
|
||||
const fixture = new ComponentFixture(App);
|
||||
new ComponentFixture(App);
|
||||
expect(dirA !.dirB).toEqual(null);
|
||||
}).not.toThrow();
|
||||
});
|
||||
|
@ -622,7 +657,7 @@ describe('di', () => {
|
|||
}, [DirA, DirB]);
|
||||
|
||||
expect(() => {
|
||||
const fixture = new ComponentFixture(App);
|
||||
new ComponentFixture(App);
|
||||
expect(dirA !.dirB).toEqual(null);
|
||||
}).not.toThrow();
|
||||
});
|
||||
|
@ -656,7 +691,7 @@ describe('di', () => {
|
|||
}
|
||||
}, [Comp, DirB]);
|
||||
|
||||
const fixture = new ComponentFixture(App);
|
||||
new ComponentFixture(App);
|
||||
expect(dirA !.dirB.value).toEqual('parent');
|
||||
});
|
||||
|
||||
|
@ -687,9 +722,7 @@ describe('di', () => {
|
|||
}
|
||||
}, [DirA, DirB]);
|
||||
|
||||
expect(() => {
|
||||
const fixture = new ComponentFixture(App);
|
||||
}).toThrowError(/Injector: NOT_FOUND \[DirB\]/);
|
||||
expect(() => { new ComponentFixture(App); }).toThrowError(/Injector: NOT_FOUND \[DirB\]/);
|
||||
});
|
||||
|
||||
it('should check only the current node with @Self even with false positive', () => {
|
||||
|
@ -724,7 +757,7 @@ describe('di', () => {
|
|||
expect(() => {
|
||||
(DirA as any)['__NG_ELEMENT_ID__'] = 1;
|
||||
(DirC as any)['__NG_ELEMENT_ID__'] = 257;
|
||||
const fixture = new ComponentFixture(App);
|
||||
new ComponentFixture(App);
|
||||
}).toThrowError(/Injector: NOT_FOUND \[DirB\]/);
|
||||
});
|
||||
|
||||
|
@ -757,9 +790,7 @@ describe('di', () => {
|
|||
}
|
||||
}, [Comp, DirB]);
|
||||
|
||||
expect(() => {
|
||||
const fixture = new ComponentFixture(App);
|
||||
}).toThrowError(/Injector: NOT_FOUND \[DirB\]/);
|
||||
expect(() => { new ComponentFixture(App); }).toThrowError(/Injector: NOT_FOUND \[DirB\]/);
|
||||
|
||||
});
|
||||
|
||||
|
@ -1214,7 +1245,7 @@ describe('di', () => {
|
|||
}
|
||||
});
|
||||
|
||||
const fixture = new ComponentFixture(MyApp);
|
||||
new ComponentFixture(MyApp);
|
||||
expect(exist).toEqual('existValue');
|
||||
expect(nonExist).toEqual(undefined);
|
||||
});
|
||||
|
@ -1232,7 +1263,7 @@ describe('di', () => {
|
|||
}
|
||||
});
|
||||
|
||||
const fixture = new ComponentFixture(MyApp);
|
||||
new ComponentFixture(MyApp);
|
||||
expect(exist).toEqual('existValue');
|
||||
expect(nonExist).toEqual(undefined);
|
||||
});
|
||||
|
@ -1251,7 +1282,7 @@ describe('di', () => {
|
|||
}
|
||||
});
|
||||
|
||||
const fixture = new ComponentFixture(MyApp);
|
||||
new ComponentFixture(MyApp);
|
||||
expect(exist).toEqual('existValue');
|
||||
expect(nonExist).toEqual(undefined);
|
||||
});
|
||||
|
|
Loading…
Reference in New Issue