diff --git a/packages/compiler/src/render3/view/compiler.ts b/packages/compiler/src/render3/view/compiler.ts index 4e6bd46be8..5774da877c 100644 --- a/packages/compiler/src/render3/view/compiler.ts +++ b/packages/compiler/src/render3/view/compiler.ts @@ -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, diff --git a/packages/core/src/di/injection_token.ts b/packages/core/src/di/injection_token.ts index f33d3a5d48..2071fb3178 100644 --- a/packages/core/src/di/injection_token.ts +++ b/packages/core/src/di/injection_token.ts @@ -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. diff --git a/packages/core/src/render3/di.ts b/packages/core/src/render3/di.ts index 3abd46573c..1b46d7dd0a 100644 --- a/packages/core/src/render3/di.ts +++ b/packages/core/src/render3/di.ts @@ -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): 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): void { * @param flags Injection flags * @returns the value from the injector or `null` when not found */ -export function directiveInject(token: Type): T; -export function directiveInject(token: Type, flags: InjectFlags.Optional): T|null; -export function directiveInject(token: Type, flags: InjectFlags): T; -export function directiveInject(token: Type, flags = InjectFlags.Default): T|null { +export function directiveInject(token: Type| InjectionToken): T; +export function directiveInject(token: Type| InjectionToken, flags: InjectFlags): T; +export function directiveInject( + token: Type| InjectionToken, flags = InjectFlags.Default): T|null { return getOrCreateInjectable(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( - di: LInjector, token: Type, flags: InjectFlags = InjectFlags.Default): T|null { + nodeInjector: LInjector, token: Type| InjectionToken, + 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( // 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(node, token))) { + if (injector === nodeInjector && + (instance = searchMatchesQueuedForCreation(node, token))) { return instance; } @@ -404,9 +399,13 @@ export function getOrCreateInjectable( } } - // 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(node: LNode, token: any): T|null { @@ -433,8 +432,8 @@ function searchMatchesQueuedForCreation(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): number|null { - let id: number|undefined = (type as any)[NG_ELEMENT_ID]; +function bloomHashBit(token: Type| InjectionToken): 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; } diff --git a/packages/core/src/render3/jit/pipe.ts b/packages/core/src/render3/jit/pipe.ts index 57abcb9d55..0fc32841da 100644 --- a/packages/core/src/render3/jit/pipe.ts +++ b/packages/core/src/render3/jit/pipe.ts @@ -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'; diff --git a/packages/core/test/render3/di_spec.ts b/packages/core/test/render3/di_spec.ts index e65ff383b7..09523157b2 100644 --- a/packages/core/test/render3/di_spec.ts +++ b/packages/core/test/render3/di_spec.ts @@ -117,7 +117,7 @@ describe('di', () => { .toEqual('
DirADirB
'); }); - 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)), + }); + } + + // `
` + // - 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); });