refactor(ivy): replace LNode.nodeInjector with TNode.injectorIndex (#26177)

PR Close #26177
This commit is contained in:
Kara Erickson 2018-09-28 21:26:45 -07:00 committed by Alex Rickabaugh
parent 2ad1bb4eb9
commit 68fadd9b97
12 changed files with 418 additions and 93 deletions

View File

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

View File

@ -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<any>): 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 <ng-template> 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.

View File

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

View File

@ -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<any>;
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<T>(
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<T>(
* (same properties assigned in the same order).
*/
export function createLNodeObject(
type: TNodeType, nodeInjector: LInjector | null, native: RText | RElement | RComment | null,
state: any): LElementNode&LTextNode&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<T>(
*/
export function createEmbeddedViewAndNode<T>(
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<T>(
}
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,

View File

@ -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:
*

View File

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

View File

@ -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<any>
_renderer: Renderer3, _queries: LQueries | null, _injectorIndex: number):
ViewEngine_TemplateRef<any>
};
/**
@ -96,7 +97,8 @@ export function createTemplateRef<T>(
R3TemplateRef = class TemplateRef_<T> extends TemplateRefToken<T> {
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<T>(
context: T, container?: LContainer, tContainerNode?: TContainerNode, hostView?: LViewData,
index?: number): viewEngine_EmbeddedViewRef<T> {
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<T>(
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);

View File

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

View File

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

View File

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

View File

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

View File

@ -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'
});
}
/**
* <div dirA #dir="dirA">
* {{ dir.dirB.value }}
* </div>
*/
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 dirB>/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));
}
}
/**
* <div dirB>
* <div *ngIf="showing">
* <div dirA #dir="dirA"> {{ dir.dirB.value }} </div>
* </div>
* </div>
*/
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', () => {
/**
* <div dirB>
* % if (!skipContent) {
* % if (!skipContent2) {
* <div dirA #dir="dirA"> {{ dir.dirB.value }} </div>
* % }
* % }
* </div>
*/
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<any>;
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));
}
}
/**
* <div dirB value="declaration">
* <ng-template #foo>
* <div dirA dir="dirA"> {{ dir.dirB.value }} </div>
* </ng-template>
* </div>
*
* <div dirB value="insertion">
* <div structuralDir [tmp]="foo"></div>
* // insertion point
* </div>
*/
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', () => {
/**
* <comp dirB></comp>
* <comp dirB></comp>
*/
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<HostBindingDir>(directiveIndex).id));
}
});
}
let dir !: DirA;
/**
* <div dirB hostBindingDir>
* <p dirA #dir="dirA">
* {{ dir.dirB.value }}
* </p>
* </div>
*/
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';