From 68fadd9b97823128b60c68334c9ebc0dcc6f32ef Mon Sep 17 00:00:00 2001 From: Kara Erickson Date: Fri, 28 Sep 2018 21:26:45 -0700 Subject: [PATCH] refactor(ivy): replace LNode.nodeInjector with TNode.injectorIndex (#26177) PR Close #26177 --- packages/core/src/render3/debug.ts | 6 +- packages/core/src/render3/di.ts | 63 ++-- packages/core/src/render3/i18n.ts | 5 +- packages/core/src/render3/instructions.ts | 44 ++- packages/core/src/render3/interfaces/node.ts | 18 +- .../core/src/render3/node_manipulation.ts | 12 - .../src/render3/view_engine_compatibility.ts | 23 +- .../bundle.golden_symbols.json | 7 +- .../hello_world/bundle.golden_symbols.json | 15 +- .../bundling/todo/bundle.golden_symbols.json | 7 +- .../todo_r2/bundle.golden_symbols.json | 7 +- packages/core/test/render3/di_spec.ts | 304 +++++++++++++++++- 12 files changed, 418 insertions(+), 93 deletions(-) diff --git a/packages/core/src/render3/debug.ts b/packages/core/src/render3/debug.ts index 36302daedd..5246ab027c 100644 --- a/packages/core/src/render3/debug.ts +++ b/packages/core/src/render3/debug.ts @@ -14,7 +14,6 @@ import {DebugRenderer2, DebugRendererFactory2} from '../view/services'; import {getLElementNode} from './context_discovery'; import * as di from './di'; import {_getViewData} from './instructions'; -import {LElementNode} from './interfaces/node'; import {CONTEXT, DIRECTIVES, LViewData, TVIEW} from './interfaces/view'; /** @@ -47,9 +46,8 @@ class Render3DebugContext implements DebugContext { get injector(): Injector { if (this.nodeIndex !== null) { - const lElementNode: LElementNode = this.view[this.nodeIndex]; - const nodeInjector = lElementNode.nodeInjector; - + const tNode = this.view[TVIEW].data[this.nodeIndex]; + const nodeInjector = di.getInjector(tNode, this.view); if (nodeInjector) { return new di.NodeInjector(nodeInjector); } diff --git a/packages/core/src/render3/di.ts b/packages/core/src/render3/di.ts index 4db47834a4..4c7dae2094 100644 --- a/packages/core/src/render3/di.ts +++ b/packages/core/src/render3/di.ts @@ -15,17 +15,16 @@ import {InjectFlags, Injector, NullInjector, inject, setCurrentInjector} from '. import {Renderer2} from '../render'; import {Type} from '../type'; -import {assertDefined, assertGreaterThan, assertLessThan} from './assert'; +import {assertDefined} from './assert'; import {getComponentDef, getDirectiveDef, getPipeDef} from './definition'; import {NG_ELEMENT_ID} from './fields'; -import {_getViewData, addToViewTree, assertPreviousIsParent, createEmbeddedViewAndNode, createLContainer, createLNodeObject, createTNode, getPreviousOrParentNode, getPreviousOrParentTNode, getRenderer, loadElement, renderEmbeddedTemplate, resolveDirective, setEnvironment} from './instructions'; -import {DirectiveDefInternal, RenderFlags} from './interfaces/definition'; +import {_getViewData, assertPreviousIsParent, getPreviousOrParentTNode, resolveDirective, setEnvironment} from './instructions'; +import {DirectiveDefInternal} from './interfaces/definition'; import {LInjector} from './interfaces/injector'; -import {AttributeMarker, LContainerNode, LElementContainerNode, LElementNode, LNode, TContainerNode, TElementContainerNode, TElementNode, TNode, TNodeFlags, TNodeType, TViewNode} from './interfaces/node'; -import {Renderer3, isProceduralRenderer} from './interfaces/renderer'; -import {CONTEXT, DIRECTIVES, HOST_NODE, INJECTOR, LViewData, QUERIES, RENDERER, TVIEW, TView} from './interfaces/view'; -import {assertNodeOfPossibleTypes, assertNodeType} from './node_assert'; -import {addRemoveViewFromContainer, appendChild, detachView, findComponentView, getBeforeNodeForView, getHostElementNode, getParentLNode, getParentOrContainerNode, getRenderParent, insertView, removeView} from './node_manipulation'; +import {AttributeMarker, TContainerNode, TElementContainerNode, TElementNode, TNode, TNodeFlags, TNodeType} from './interfaces/node'; +import {isProceduralRenderer} from './interfaces/renderer'; +import {DECLARATION_VIEW, DIRECTIVES, HOST_NODE, INJECTOR, LViewData, RENDERER, TVIEW, TView} from './interfaces/view'; +import {assertNodeOfPossibleTypes} from './node_assert'; /** * The number of slots in each bloom filter (used by DI). The larger this number, the fewer @@ -81,7 +80,6 @@ export function bloomAdd(injector: LInjector, type: Type): void { export function getOrCreateNodeInjector(): LInjector { ngDevMode && assertPreviousIsParent(); return getOrCreateNodeInjectorForNode( - getPreviousOrParentNode() as LElementNode | LElementContainerNode | LContainerNode, getPreviousOrParentTNode() as TElementNode | TElementContainerNode | TContainerNode, _getViewData()); } @@ -89,22 +87,25 @@ export function getOrCreateNodeInjector(): LInjector { /** * Creates (or gets an existing) injector for a given element or container. * - * @param node for which an injector should be retrieved / created. * @param tNode for which an injector should be retrieved / created. * @param hostView View where the node is stored * @returns Node injector */ export function getOrCreateNodeInjectorForNode( - node: LElementNode | LElementContainerNode | LContainerNode, tNode: TElementNode | TContainerNode | TElementContainerNode, hostView: LViewData): LInjector { - // TODO: remove LNode arg when nodeInjector refactor is done - const nodeInjector = node.nodeInjector; - const parentLNode = getParentOrContainerNode(tNode, hostView); - const parentInjector = parentLNode && parentLNode.nodeInjector; - if (nodeInjector != parentInjector) { - return nodeInjector !; + const injector = getInjector(tNode, hostView); + if (injector) return injector; + + const tView = hostView[TVIEW]; + if (tView.firstTemplatePass) { + // TODO(kara): Store node injector with host bindings for that node (see VIEW_DATA.md) + tNode.injectorIndex = hostView.length; + tView.blueprint.push(null); + tView.hostBindingStartIndex++; } - return node.nodeInjector = { + + const parentInjector = getParentInjector(tNode, hostView); + return hostView[tNode.injectorIndex] = { parent: parentInjector, tNode: tNode, view: hostView, @@ -127,6 +128,32 @@ export function getOrCreateNodeInjectorForNode( }; } +export function getInjector(tNode: TNode, view: LViewData): LInjector|null { + // If the injector index is the same as its parent's injector index, then the index has been + // copied down from the parent node. No injector has been created yet on this node. + if (tNode.injectorIndex === -1 || + tNode.parent && tNode.parent.injectorIndex === tNode.injectorIndex) { + return null; + } else { + return view[tNode.injectorIndex]; + } +} + +export function getParentInjector(tNode: TNode, view: LViewData): LInjector { + if (tNode.parent && tNode.parent.injectorIndex !== -1) { + return view[tNode.parent.injectorIndex]; + } + + // For most cases, the parent injector index can be found on the host node (e.g. for component + // or container), so this loop will be skipped, but we must keep the loop here to support + // the rarer case of deeply nested tags. + let hostTNode = view[HOST_NODE]; + while (hostTNode && hostTNode.injectorIndex === -1) { + view = view[DECLARATION_VIEW] !; + hostTNode = view[HOST_NODE] !; + } + return hostTNode ? view[DECLARATION_VIEW] ![hostTNode.injectorIndex] : null; +} /** * Makes a directive public to the DI system by adding it to an injector's bloom filter. diff --git a/packages/core/src/render3/i18n.ts b/packages/core/src/render3/i18n.ts index 1cdf5fe758..e5a0e8b126 100644 --- a/packages/core/src/render3/i18n.ts +++ b/packages/core/src/render3/i18n.ts @@ -9,10 +9,9 @@ import {assertEqual, assertLessThan} from './assert'; import {NO_CHANGE, _getViewData, adjustBlueprintForNewNode, bindingUpdated, bindingUpdated2, bindingUpdated3, bindingUpdated4, createNodeAtIndex, getRenderer, getTNode, load, loadElement, resetComponentState} from './instructions'; import {RENDER_PARENT} from './interfaces/container'; -import {LContainerNode, LElementNode, LNode, TElementNode, TNode, TNodeType, TViewNode} from './interfaces/node'; -import {RElement} from './interfaces/renderer'; +import {LContainerNode, LNode, TElementNode, TNode, TNodeType} from './interfaces/node'; import {BINDING_INDEX, HEADER_OFFSET, HOST_NODE, TVIEW} from './interfaces/view'; -import {appendChild, createTextNode, getContainerRenderParent, getParentLNode, getParentOrContainerNode, removeChild} from './node_manipulation'; +import {appendChild, createTextNode, removeChild} from './node_manipulation'; import {stringify} from './util'; /** diff --git a/packages/core/src/render3/instructions.ts b/packages/core/src/render3/instructions.ts index 1a6891be82..d88f049d6f 100644 --- a/packages/core/src/render3/instructions.ts +++ b/packages/core/src/render3/instructions.ts @@ -25,7 +25,7 @@ import {LQueries} from './interfaces/query'; import {ProceduralRenderer3, RComment, RElement, RNode, RText, Renderer3, RendererFactory3, isProceduralRenderer} from './interfaces/renderer'; import {BINDING_INDEX, CLEANUP, CONTAINER_INDEX, CONTENT_QUERIES, CONTEXT, CurrentMatchesList, DECLARATION_VIEW, DIRECTIVES, FLAGS, HEADER_OFFSET, HOST_NODE, INJECTOR, LViewData, LViewFlags, NEXT, OpaqueViewState, PARENT, QUERIES, RENDERER, RootContext, RootContextFlags, SANITIZER, TAIL, TVIEW, TView} from './interfaces/view'; import {assertNodeOfPossibleTypes, assertNodeType} from './node_assert'; -import {appendChild, appendProjectedNode, createTextNode, findComponentView, getContainerNode, getHostElementNode, getLViewChild, getParentOrContainerNode, getRenderParent, insertView, removeView} from './node_manipulation'; +import {appendChild, appendProjectedNode, createTextNode, findComponentView, getHostElementNode, getLViewChild, getRenderParent, insertView, removeView} from './node_manipulation'; import {isNodeMatchingSelectorList, matchingSelectorIndex} from './node_selector_matcher'; import {StylingContext, allocStylingContext, createStylingContextTemplate, renderStyling as renderElementStyles, updateClassProp as updateElementClassProp, updateStyleProp as updateElementStyleProp, updateStylingMap} from './styling'; import {assertDataInRangeInternal, getLNode, isContentQueryHost, isDifferent, loadElementInternal, loadInternal, stringify} from './util'; @@ -333,6 +333,8 @@ export function leaveView(newView: LViewData, creationOnly?: boolean): void { * Note: view hooks are triggered later when leaving the view. */ function refreshDescendantViews() { + setHostBindings(tView.hostBindings); + // This needs to be set before children are processed to support recursive components tView.firstTemplatePass = firstTemplatePass = false; @@ -348,7 +350,6 @@ function refreshDescendantViews() { executeHooks(directives !, tView.contentHooks, tView.contentCheckHooks, creationMode); } - setHostBindings(tView.hostBindings); refreshChildComponents(tView.components); } @@ -361,6 +362,12 @@ export function setHostBindings(bindings: number[] | null): void { for (let i = 0; i < bindings.length; i += 2) { const dirIndex = bindings[i]; const def = defs[dirIndex] as DirectiveDefInternal; + if (firstTemplatePass) { + for (let i = 0; i < def.hostVars; i++) { + tView.blueprint.push(NO_CHANGE); + viewData.push(NO_CHANGE); + } + } def.hostBindings !(dirIndex, bindings[i + 1]); bindingRootIndex = viewData[BINDING_INDEX] = bindingRootIndex + def.hostVars; } @@ -399,7 +406,7 @@ export function createLViewData( renderer: Renderer3, tView: TView, context: T | null, flags: LViewFlags, sanitizer?: Sanitizer | null): LViewData { const instance = tView.blueprint.slice() as LViewData; - instance[PARENT] = viewData; + instance[PARENT] = instance[DECLARATION_VIEW] = viewData; instance[FLAGS] = flags | LViewFlags.CreationMode | LViewFlags.Attached | LViewFlags.RunInit; instance[CONTEXT] = context; instance[INJECTOR] = viewData ? viewData[INJECTOR] : null; @@ -414,14 +421,9 @@ export function createLViewData( * (same properties assigned in the same order). */ export function createLNodeObject( - type: TNodeType, nodeInjector: LInjector | null, native: RText | RElement | RComment | null, - state: any): LElementNode<extNode&LViewNode&LContainerNode&LProjectionNode { - return { - native: native as any, - nodeInjector: nodeInjector, - data: state, - dynamicLContainerNode: null - }; + type: TNodeType, native: RText | RElement | RComment | null, state: any): LElementNode& + LTextNode&LViewNode&LContainerNode&LProjectionNode { + return {native: native as any, data: state, dynamicLContainerNode: null}; } /** @@ -464,7 +466,7 @@ export function createNodeAtIndex( const tParent = parentInSameView ? parent as TElementNode | TContainerNode : null; const isState = state != null; - const node = createLNodeObject(type, null, native, isState ? state as any : null); + const node = createLNodeObject(type, native, isState ? state as any : null); let tNode: TNode; if (index === -1 || type === TNodeType.View) { @@ -506,13 +508,6 @@ export function createNodeAtIndex( } } } - // TODO: temporary, remove this when removing nodeInjector (bringing in fns to hello world) - if (index !== -1 && !(viewData[FLAGS] & LViewFlags.IsRoot)) { - const parentLNode: LNode|null = type === TNodeType.View ? - getContainerNode(tNode, state as LViewData) : - getParentOrContainerNode(tNode, viewData); - parentLNode && (node.nodeInjector = parentLNode.nodeInjector); - } // View nodes and host elements need to set their host node (components do not save host TNodes) if ((type & TNodeType.ViewOrElement) === TNodeType.ViewOrElement && isState) { @@ -607,7 +602,7 @@ export function renderTemplate( */ export function createEmbeddedViewAndNode( tView: TView, context: T, declarationView: LViewData, renderer: Renderer3, - queries?: LQueries | null): LViewData { + queries: LQueries | null, injectorIndex: number): LViewData { const _isParent = isParent; const _previousOrParentTNode = previousOrParentTNode; isParent = true; @@ -622,6 +617,10 @@ export function createEmbeddedViewAndNode( } createNodeAtIndex(-1, TNodeType.View, null, null, null, lView); + if (tView.firstTemplatePass) { + tView.node !.injectorIndex = injectorIndex; + } + isParent = _isParent; previousOrParentTNode = _previousOrParentTNode; return lView; @@ -969,10 +968,6 @@ export function queueHostBindingForCheck(dirIndex: number, hostVars: number): vo // instructions that expect element indices that are NOT adjusted (e.g. elementProperty). ngDevMode && assertEqual(firstTemplatePass, true, 'Should only be called in first template pass.'); - for (let i = 0; i < hostVars; i++) { - tView.blueprint.push(NO_CHANGE); - viewData.push(NO_CHANGE); - } (tView.hostBindings || (tView.hostBindings = [ ])).push(dirIndex, previousOrParentTNode.index - HEADER_OFFSET); } @@ -1476,6 +1471,7 @@ export function createTNode( return { type: type, index: adjustedIndex, + injectorIndex: parent ? parent.injectorIndex : -1, flags: 0, tagName: tagName, attrs: attrs, diff --git a/packages/core/src/render3/interfaces/node.ts b/packages/core/src/render3/interfaces/node.ts index 77bb6f6bc1..e1ef79ef49 100644 --- a/packages/core/src/render3/interfaces/node.ts +++ b/packages/core/src/render3/interfaces/node.ts @@ -81,9 +81,6 @@ export interface LNode { */ readonly data: LViewData|LContainer|null; - /** The injector associated with this node. Necessary for DI. */ - nodeInjector: LInjector|null; - /** * A pointer to an LContainerNode created by directives requesting ViewContainerRef */ @@ -196,6 +193,21 @@ export interface TNode { */ index: number; + /** + * The index of the closest injector in this node's LViewData. + * + * If the index === -1, there is no injector on this node or any ancestor node in this view. + * + * If the index !== -1, it is the index of this node's injector OR the index of a parent injector + * in the same view. We pass the parent injector index down the node tree of a view so it's + * possible to find the parent injector without walking a potentially deep node tree. Injector + * indices are not set across view boundaries because there could be multiple component hosts. + * + * If tNode.injectorIndex === tNode.parent.injectorIndex, then the index belongs to a parent + * injector. + */ + injectorIndex: number; + /** * This number stores two values using its bits: * diff --git a/packages/core/src/render3/node_manipulation.ts b/packages/core/src/render3/node_manipulation.ts index ee434159bc..2e9f432b41 100644 --- a/packages/core/src/render3/node_manipulation.ts +++ b/packages/core/src/render3/node_manipulation.ts @@ -37,18 +37,6 @@ export function getHostElementNode(currentView: LViewData): LElementNode|null { null; } -/** - * Gets the parent LNode if it's not a view. If it's a view, it will instead return the view's - * parent container node. - */ -export function getParentOrContainerNode(tNode: TNode, currentView: LViewData): LElementNode| - LElementContainerNode|LContainerNode|null { - const parentTNode = tNode.parent || currentView[HOST_NODE]; - return parentTNode && parentTNode.type === TNodeType.View ? - getContainerNode(parentTNode, currentView) : - getParentLNode(tNode, currentView); -} - export function getContainerNode(tNode: TNode, embeddedView: LViewData): LContainerNode|null { if (tNode.index === -1) { // This is a dynamically created view inside a dynamic container. diff --git a/packages/core/src/render3/view_engine_compatibility.ts b/packages/core/src/render3/view_engine_compatibility.ts index 9101be0436..a2c4d01ace 100644 --- a/packages/core/src/render3/view_engine_compatibility.ts +++ b/packages/core/src/render3/view_engine_compatibility.ts @@ -16,7 +16,7 @@ import {ViewContainerRef as ViewEngine_ViewContainerRef} from '../linker/view_co import {EmbeddedViewRef as viewEngine_EmbeddedViewRef, ViewRef as viewEngine_ViewRef} from '../linker/view_ref'; import {assertDefined, assertGreaterThan, assertLessThan} from './assert'; -import {NodeInjector, getOrCreateNodeInjectorForNode} from './di'; +import {NodeInjector, getInjector, getOrCreateNodeInjectorForNode, getParentInjector} from './di'; import {_getViewData, addToViewTree, createEmbeddedViewAndNode, createLContainer, createLNodeObject, createTNode, getPreviousOrParentTNode, getRenderer, renderEmbeddedTemplate} from './instructions'; import {LContainer, RENDER_PARENT, VIEWS} from './interfaces/container'; import {RenderFlags} from './interfaces/definition'; @@ -64,7 +64,8 @@ export function createElementRef( let R3TemplateRef: { new ( _declarationParentView: LViewData, elementRef: ViewEngine_ElementRef, _tView: TView, - _renderer: Renderer3, _queries: LQueries | null): ViewEngine_TemplateRef + _renderer: Renderer3, _queries: LQueries | null, _injectorIndex: number): + ViewEngine_TemplateRef }; /** @@ -96,7 +97,8 @@ export function createTemplateRef( R3TemplateRef = class TemplateRef_ extends TemplateRefToken { constructor( private _declarationParentView: LViewData, readonly elementRef: ViewEngine_ElementRef, - private _tView: TView, private _renderer: Renderer3, private _queries: LQueries|null) { + private _tView: TView, private _renderer: Renderer3, private _queries: LQueries|null, + private _injectorIndex: number) { super(); } @@ -104,7 +106,8 @@ export function createTemplateRef( context: T, container?: LContainer, tContainerNode?: TContainerNode, hostView?: LViewData, index?: number): viewEngine_EmbeddedViewRef { const lView = createEmbeddedViewAndNode( - this._tView, context, this._declarationParentView, this._renderer, this._queries); + this._tView, context, this._declarationParentView, this._renderer, this._queries, + this._injectorIndex); if (container) { insertView(lView, container, hostView !, index !, tContainerNode !.parent !.index); } @@ -122,7 +125,7 @@ export function createTemplateRef( ngDevMode && assertDefined(hostTNode.tViews, 'TView must be allocated'); return new R3TemplateRef( hostView, createElementRef(ElementRefToken, hostTNode, hostView), hostTNode.tViews as TView, - getRenderer(), hostNode.data ![QUERIES]); + getRenderer(), hostNode.data ![QUERIES], hostTNode.injectorIndex); } let R3ViewContainerRef: { @@ -178,15 +181,13 @@ export function createContainerRef( } get injector(): Injector { - // TODO: Remove LNode lookup when removing LNode.nodeInjector - const injector = - getOrCreateNodeInjectorForNode(this._getHostNode(), this._hostTNode, this._hostView); - return new NodeInjector(injector); + const nodeInjector = getOrCreateNodeInjectorForNode(this._hostTNode, this._hostView); + return new NodeInjector(nodeInjector); } /** @deprecated No replacement */ get parentInjector(): Injector { - const parentLInjector = getParentLNode(this._hostTNode, this._hostView) !.nodeInjector; + const parentLInjector = getParentInjector(this._hostTNode, this._hostView); return parentLInjector ? new NodeInjector(parentLInjector) : new NullInjector(); } @@ -292,7 +293,7 @@ export function createContainerRef( const lContainer = createLContainer(hostView, true); const comment = hostView[RENDERER].createComment(ngDevMode ? 'container' : ''); const lContainerNode: LContainerNode = - createLNodeObject(TNodeType.Container, hostLNode.nodeInjector, comment, lContainer); + createLNodeObject(TNodeType.Container, comment, lContainer); lContainer[RENDER_PARENT] = getRenderParent(hostTNode, hostView); diff --git a/packages/core/test/bundling/animation_world/bundle.golden_symbols.json b/packages/core/test/bundling/animation_world/bundle.golden_symbols.json index 56c16bd36e..33f28bf3f6 100644 --- a/packages/core/test/bundling/animation_world/bundle.golden_symbols.json +++ b/packages/core/test/bundling/animation_world/bundle.golden_symbols.json @@ -581,6 +581,9 @@ { "name": "getInjectableDef" }, + { + "name": "getInjector$1" + }, { "name": "getLElementFromComponent" }, @@ -618,10 +621,10 @@ "name": "getOrCreateTView" }, { - "name": "getParentLNode" + "name": "getParentInjector" }, { - "name": "getParentOrContainerNode" + "name": "getParentLNode" }, { "name": "getParentState" diff --git a/packages/core/test/bundling/hello_world/bundle.golden_symbols.json b/packages/core/test/bundling/hello_world/bundle.golden_symbols.json index 09f41930ad..51ffa47f74 100644 --- a/packages/core/test/bundling/hello_world/bundle.golden_symbols.json +++ b/packages/core/test/bundling/hello_world/bundle.golden_symbols.json @@ -20,6 +20,9 @@ { "name": "ChangeDetectionStrategy" }, + { + "name": "DECLARATION_VIEW" + }, { "name": "DIRECTIVES" }, @@ -236,6 +239,9 @@ { "name": "getHostElementNode" }, + { + "name": "getInjector$1" + }, { "name": "getLNode" }, @@ -251,18 +257,15 @@ { "name": "getOrCreateTView" }, + { + "name": "getParentInjector" + }, { "name": "getParentLNode" }, - { - "name": "getParentOrContainerNode" - }, { "name": "getPipeDef" }, - { - "name": "getPreviousOrParentNode" - }, { "name": "getPreviousOrParentTNode" }, diff --git a/packages/core/test/bundling/todo/bundle.golden_symbols.json b/packages/core/test/bundling/todo/bundle.golden_symbols.json index d675c278a4..6878df5edd 100644 --- a/packages/core/test/bundling/todo/bundle.golden_symbols.json +++ b/packages/core/test/bundling/todo/bundle.golden_symbols.json @@ -623,6 +623,9 @@ { "name": "getInjectableDef" }, + { + "name": "getInjector$1" + }, { "name": "getLElementFromComponent" }, @@ -654,10 +657,10 @@ "name": "getOrCreateTView" }, { - "name": "getParentLNode" + "name": "getParentInjector" }, { - "name": "getParentOrContainerNode" + "name": "getParentLNode" }, { "name": "getParentState" diff --git a/packages/core/test/bundling/todo_r2/bundle.golden_symbols.json b/packages/core/test/bundling/todo_r2/bundle.golden_symbols.json index 3a8404665e..18679254fe 100644 --- a/packages/core/test/bundling/todo_r2/bundle.golden_symbols.json +++ b/packages/core/test/bundling/todo_r2/bundle.golden_symbols.json @@ -1652,6 +1652,9 @@ { "name": "getInjectableDef" }, + { + "name": "getInjector$1" + }, { "name": "getInjectorDef" }, @@ -1749,10 +1752,10 @@ "name": "getOriginalError" }, { - "name": "getParentLNode" + "name": "getParentInjector" }, { - "name": "getParentOrContainerNode" + "name": "getParentLNode" }, { "name": "getParentState" diff --git a/packages/core/test/render3/di_spec.ts b/packages/core/test/render3/di_spec.ts index 9a87e4c76b..89cb474285 100644 --- a/packages/core/test/render3/di_spec.ts +++ b/packages/core/test/render3/di_spec.ts @@ -6,19 +6,19 @@ * found in the LICENSE file at https://angular.io/license */ -import {Attribute, ChangeDetectorRef, ElementRef, Host, InjectFlags, Optional, Renderer2, Self, SkipSelf, TemplateRef, ViewContainerRef, defineInjectable} from '@angular/core'; +import {Attribute, ChangeDetectorRef, ElementRef, Host, InjectFlags, Injector, Optional, Renderer2, Self, SkipSelf, TemplateRef, ViewContainerRef, defineInjectable} from '@angular/core'; import {RenderFlags} from '@angular/core/src/render3/interfaces/definition'; import {defineComponent} from '../../src/render3/definition'; -import {bloomAdd, bloomFindPossibleInjector, getOrCreateNodeInjector, injectAttribute} from '../../src/render3/di'; -import {PublicFeature, defineDirective, directiveInject, injectRenderer2, load} from '../../src/render3/index'; +import {bloomAdd, bloomFindPossibleInjector, getInjector, getOrCreateNodeInjector, injectAttribute} from '../../src/render3/di'; +import {PublicFeature, defineDirective, directiveInject, elementProperty, getCurrentView, getRenderedText, injectRenderer2, load, templateRefExtractor} from '../../src/render3/index'; -import {bind, container, containerRefreshEnd, containerRefreshStart, createNodeAtIndex, createLViewData, createTView, element, elementEnd, elementStart, embeddedViewEnd, embeddedViewStart, enterView, interpolation2, leaveView, projection, projectionDef, reference, template, text, textBinding, loadDirective, elementContainerStart, elementContainerEnd} from '../../src/render3/instructions'; +import {bind, container, containerRefreshEnd, containerRefreshStart, createNodeAtIndex, createLViewData, createTView, element, elementEnd, elementStart, embeddedViewEnd, embeddedViewStart, enterView, interpolation2, leaveView, projection, projectionDef, reference, template, text, textBinding, loadDirective, elementContainerStart, elementContainerEnd, _getViewData, getTNode} from '../../src/render3/instructions'; import {LInjector} from '../../src/render3/interfaces/injector'; import {isProceduralRenderer} from '../../src/render3/interfaces/renderer'; import {AttributeMarker, LContainerNode, LElementNode, TNodeType} from '../../src/render3/interfaces/node'; -import {LViewFlags} from '../../src/render3/interfaces/view'; +import {HEADER_OFFSET, LViewData, LViewFlags, TVIEW, TView} from '../../src/render3/interfaces/view'; import {ViewRef} from '../../src/render3/view_ref'; import {getRendererFactory2} from './imported_renderer2'; @@ -67,7 +67,8 @@ describe('di', () => { selectors: [['', 'dirB', '']], type: DirB, factory: () => new DirB(), - features: [PublicFeature] + features: [PublicFeature], + inputs: {value: 'value'} }); } @@ -433,6 +434,297 @@ describe('di', () => { expect(log).toEqual(['DirB', 'DirB', 'DirA (dep: DirB - 2)']); }); + describe('dependencies in parent views', () => { + + class DirA { + injector: Injector; + constructor(public dirB: DirB, public vcr: ViewContainerRef) { + this.injector = vcr.injector; + } + + static ngDirectiveDef = defineDirective({ + type: DirA, + selectors: [['', 'dirA', '']], + factory: () => new DirA(directiveInject(DirB), directiveInject(ViewContainerRef as any)), + features: [PublicFeature], + exportAs: 'dirA' + }); + } + + /** + *
+ * {{ dir.dirB.value }} + *
+ */ + const Comp = createComponent('comp', (rf: RenderFlags, ctx: any) => { + if (rf & RenderFlags.Create) { + elementStart(0, 'div', ['dirA', ''], ['dir', 'dirA']); + { text(2); } + elementEnd(); + } + if (rf & RenderFlags.Update) { + const dir = reference(1) as DirA; + textBinding(2, bind(dir.dirB.value)); + } + }, 3, 1, [DirA]); + + it('should find dependencies on component hosts', () => { + /** /comp> */ + const App = createComponent('app', (rf: RenderFlags, ctx: any) => { + if (rf & RenderFlags.Create) { + element(0, 'comp', ['dirB', '']); + } + }, 1, 0, [Comp, DirB]); + + const fixture = new ComponentFixture(App); + expect(fixture.hostElement.textContent).toEqual(`DirB`); + }); + + it('should find dependencies for directives in embedded views', () => { + + function IfTemplate(rf: RenderFlags, ctx: any) { + if (rf & RenderFlags.Create) { + elementStart(0, 'div'); + { + elementStart(1, 'div', ['dirA', ''], ['dir', 'dirA']); + { text(3); } + elementEnd(); + } + elementEnd(); + } + + if (rf & RenderFlags.Update) { + const dir = reference(2) as DirA; + textBinding(3, bind(dir.dirB.value)); + } + } + + /** + *
+ *
+ *
{{ dir.dirB.value }}
+ *
+ *
+ */ + const App = createComponent('app', (rf: RenderFlags, ctx: any) => { + if (rf & RenderFlags.Create) { + elementStart(0, 'div', ['dirB', '']); + { template(1, IfTemplate, 4, 1, '', [AttributeMarker.SelectOnly, 'ngIf', '']); } + elementEnd(); + } + if (rf & RenderFlags.Update) { + elementProperty(1, 'ngIf', bind(ctx.showing)); + } + }, 2, 1, [DirA, DirB, NgIf]); + + const fixture = new ComponentFixture(App); + fixture.component.showing = true; + fixture.update(); + + expect(fixture.hostElement.textContent).toEqual(`DirB`); + }); + + it('should find dependencies of directives nested deeply in inline views', () => { + /** + *
+ * % if (!skipContent) { + * % if (!skipContent2) { + *
{{ dir.dirB.value }}
+ * % } + * % } + *
+ */ + const App = createComponent('app', (rf: RenderFlags, ctx: any) => { + if (rf & RenderFlags.Create) { + elementStart(0, 'div', ['dirB', '']); + { container(1); } + elementEnd(); + } + if (rf & RenderFlags.Update) { + containerRefreshStart(1); + { + if (!ctx.skipContent) { + let rf1 = embeddedViewStart(0, 1, 0); + { + if (rf1 & RenderFlags.Create) { + container(0); + } + if (rf1 & RenderFlags.Update) { + containerRefreshStart(0); + { + if (!ctx.skipContent2) { + let rf2 = embeddedViewStart(0, 3, 1); + { + if (rf2 & RenderFlags.Create) { + elementStart(0, 'div', ['dirA', ''], ['dir', 'dirA']); + { text(2); } + elementEnd(); + } + if (rf2 & RenderFlags.Update) { + const dir = reference(1) as DirA; + textBinding(2, bind(dir.dirB.value)); + } + } + embeddedViewEnd(); + } + } + containerRefreshEnd(); + } + } + embeddedViewEnd(); + } + } + containerRefreshEnd(); + } + }, 2, 0, [DirA, DirB]); + + const fixture = new ComponentFixture(App); + expect(fixture.hostElement.textContent).toEqual(`DirB`); + }); + + it('should find dependencies in declaration tree of ng-template (not insertion tree)', () => { + let structuralDir !: StructuralDir; + + class StructuralDir { + // @Input() + tmp !: TemplateRef; + + constructor(public vcr: ViewContainerRef) {} + + create() { this.vcr.createEmbeddedView(this.tmp); } + + static ngDirectiveDef = defineDirective({ + type: StructuralDir, + selectors: [['', 'structuralDir', '']], + factory: () => structuralDir = + new StructuralDir(directiveInject(ViewContainerRef as any)), + inputs: {tmp: 'tmp'}, + features: [PublicFeature] + }); + } + + function FooTemplate(rf: RenderFlags, ctx: any) { + if (rf & RenderFlags.Create) { + elementStart(0, 'div', ['dirA', ''], ['dir', 'dirA']); + { text(2); } + elementEnd(); + } + if (rf & RenderFlags.Update) { + const dir = reference(1) as DirA; + textBinding(2, bind(dir.dirB.value)); + } + } + + /** + *
+ * + *
{{ dir.dirB.value }}
+ *
+ *
+ * + *
+ *
+ * // insertion point + *
+ */ + const App = createComponent('app', (rf: RenderFlags, ctx: any) => { + if (rf & RenderFlags.Create) { + elementStart(0, 'div', ['dirB', '', 'value', 'declaration']); + { template(1, FooTemplate, 3, 1, '', null, ['foo', ''], templateRefExtractor); } + elementEnd(); + elementStart(3, 'div', ['dirB', '', 'value', 'insertion']); + { element(4, 'div', ['structuralDir', '']); } + elementEnd(); + } + if (rf & RenderFlags.Update) { + const foo = reference(2) as any; + elementProperty(4, 'tmp', bind(foo)); + } + }, 5, 1, [DirA, DirB, StructuralDir]); + + const fixture = new ComponentFixture(App); + structuralDir.create(); + fixture.update(); + expect(fixture.hostElement.textContent).toEqual(`declaration`); + }); + + it('should create injectors on second template pass', () => { + /** + * + * + */ + const App = createComponent('app', (rf: RenderFlags, ctx: any) => { + if (rf & RenderFlags.Create) { + element(0, 'comp', ['dirB', '']); + element(1, 'comp', ['dirB', '']); + } + }, 2, 0, [Comp, DirB]); + + const fixture = new ComponentFixture(App); + expect(fixture.hostElement.textContent).toEqual(`DirBDirB`); + }); + + it('should create injectors and host bindings in same view', () => { + let hostBindingDir !: HostBindingDir; + + class HostBindingDir { + // @HostBinding('id') + id = 'foo'; + + static ngDirectiveDef = defineDirective({ + type: HostBindingDir, + selectors: [['', 'hostBindingDir', '']], + factory: () => hostBindingDir = new HostBindingDir(), + hostVars: 1, + hostBindings: (directiveIndex: number, elementIndex: number) => { + elementProperty( + elementIndex, 'id', bind(loadDirective(directiveIndex).id)); + } + }); + } + + let dir !: DirA; + /** + *
+ *

+ * {{ dir.dirB.value }} + *

+ *
+ */ + const App = createComponent('app', (rf: RenderFlags, ctx: any) => { + if (rf & RenderFlags.Create) { + elementStart(0, 'div', [ + 'dirB', + '', + 'hostBindingDir', + '', + ]); + { + elementStart(1, 'p', ['dirA', ''], ['dir', 'dirA']); + { text(3); } + elementEnd(); + } + elementEnd(); + } + if (rf & RenderFlags.Update) { + dir = reference(2) as DirA; + textBinding(3, bind(dir.dirB.value)); + } + }, 4, 1, [HostBindingDir, DirA, DirB]); + + const fixture = new ComponentFixture(App); + expect(fixture.hostElement.textContent).toEqual(`DirB`); + const hostDirEl = fixture.hostElement.querySelector('div') as HTMLElement; + expect(hostDirEl.id).toEqual('foo'); + // The injector should not be overwritten by host bindings + expect(dir.vcr.injector).toEqual(dir.injector); + + hostBindingDir.id = 'bar'; + fixture.update(); + expect(hostDirEl.id).toEqual('bar'); + }); + }); + it('should create instance even when no injector present', () => { class MyService { value = 'MyService';