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:
Victor Berchet 2018-07-27 09:56:35 -07:00 committed by Miško Hevery
parent d3c0915598
commit 15df853622
5 changed files with 83 additions and 56 deletions

View File

@ -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,

View File

@ -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.

View File

@ -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;
}

View File

@ -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';

View File

@ -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);
});