refactor(ivy): replace LNode.nodeInjector with TNode.injectorIndex (#26177)
PR Close #26177
This commit is contained in:
parent
2ad1bb4eb9
commit
68fadd9b97
|
@ -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);
|
||||
}
|
||||
|
|
|
@ -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.
|
||||
|
|
|
@ -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';
|
||||
|
||||
/**
|
||||
|
|
|
@ -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<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<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,
|
||||
|
|
|
@ -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:
|
||||
*
|
||||
|
|
|
@ -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.
|
||||
|
|
|
@ -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);
|
||||
|
||||
|
|
|
@ -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"
|
||||
|
|
|
@ -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"
|
||||
},
|
||||
|
|
|
@ -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"
|
||||
|
|
|
@ -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"
|
||||
|
|
|
@ -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';
|
||||
|
|
Loading…
Reference in New Issue