fix(ivy): flatten template fns for nested views (#24943)
PR Close #24943
This commit is contained in:

committed by
Igor Minar

parent
9a6d26e05b
commit
87419097da
@ -122,6 +122,8 @@ export function renderComponent<T>(
|
||||
// Create directive instance with factory() and store at index 0 in directives array
|
||||
rootContext.components.push(
|
||||
component = baseDirectiveCreate(0, componentDef.factory(), componentDef) as T);
|
||||
|
||||
(elementNode.data as LViewData)[CONTEXT] = component;
|
||||
initChangeDetectorIfExisting(elementNode.nodeInjector, component, elementNode.data !);
|
||||
|
||||
opts.hostFeatures && opts.hostFeatures.forEach((feature) => feature(component, componentDef));
|
||||
|
@ -24,7 +24,7 @@ import {LInjector} from './interfaces/injector';
|
||||
import {AttributeMarker, LContainerNode, LElementNode, LNode, LViewNode, TContainerNode, TElementNode, TNodeFlags, TNodeType} from './interfaces/node';
|
||||
import {LQueries, QueryReadType} from './interfaces/query';
|
||||
import {Renderer3} from './interfaces/renderer';
|
||||
import {DIRECTIVES, HOST_NODE, INJECTOR, LViewData, QUERIES, RENDERER, TVIEW, TView} from './interfaces/view';
|
||||
import {DECLARATION_PARENT, DIRECTIVES, HOST_NODE, INJECTOR, LViewData, QUERIES, RENDERER, TVIEW, TView} from './interfaces/view';
|
||||
import {assertNodeOfPossibleTypes, assertNodeType} from './node_assert';
|
||||
import {addRemoveViewFromContainer, appendChild, detachView, getChildLNode, getParentLNode, insertView, removeView} from './node_manipulation';
|
||||
import {ViewRef} from './view_ref';
|
||||
@ -728,7 +728,7 @@ export function getOrCreateTemplateRef<T>(di: LInjector): viewEngine.TemplateRef
|
||||
const hostTNode = hostNode.tNode;
|
||||
ngDevMode && assertDefined(hostTNode.tViews, 'TView must be allocated');
|
||||
di.templateRef = new TemplateRef<any>(
|
||||
getOrCreateElementRef(di), hostTNode.tViews as TView, getRenderer(),
|
||||
hostNode.view, getOrCreateElementRef(di), hostTNode.tViews as TView, getRenderer(),
|
||||
hostNode.data[QUERIES]);
|
||||
}
|
||||
return di.templateRef;
|
||||
@ -738,14 +738,15 @@ class TemplateRef<T> implements viewEngine.TemplateRef<T> {
|
||||
readonly elementRef: viewEngine.ElementRef;
|
||||
|
||||
constructor(
|
||||
elementRef: viewEngine.ElementRef, private _tView: TView, private _renderer: Renderer3,
|
||||
private _declarationParentView: LViewData, elementRef: viewEngine.ElementRef, private _tView: TView, private _renderer: Renderer3,
|
||||
private _queries: LQueries|null) {
|
||||
this.elementRef = elementRef;
|
||||
}
|
||||
|
||||
createEmbeddedView(context: T, containerNode?: LContainerNode, index?: number):
|
||||
viewEngine.EmbeddedViewRef<T> {
|
||||
const viewNode = createEmbeddedViewNode(this._tView, context, this._renderer, this._queries);
|
||||
const viewNode = createEmbeddedViewNode(
|
||||
this._tView, context, this._declarationParentView, this._renderer, this._queries);
|
||||
if (containerNode) {
|
||||
insertView(containerNode, viewNode, index !);
|
||||
}
|
||||
|
@ -22,7 +22,7 @@ import {AttributeMarker, InitialInputData, InitialInputs, LContainerNode, LEleme
|
||||
import {CssSelectorList, NG_PROJECT_AS_ATTR_NAME} from './interfaces/projection';
|
||||
import {LQueries} from './interfaces/query';
|
||||
import {ProceduralRenderer3, RComment, RElement, RText, Renderer3, RendererFactory3, RendererStyleFlags3, isProceduralRenderer} from './interfaces/renderer';
|
||||
import {BINDING_INDEX, CLEANUP, CONTAINER_INDEX, CONTENT_QUERIES, CONTEXT, CurrentMatchesList, DIRECTIVES, FLAGS, HEADER_OFFSET, HOST_NODE, INJECTOR, LViewData, LViewFlags, NEXT, PARENT, QUERIES, RENDERER, RootContext, SANITIZER, TAIL, TData, TVIEW, TView} from './interfaces/view';
|
||||
import {BINDING_INDEX, CLEANUP, CONTAINER_INDEX, CONTENT_QUERIES, CONTEXT, CurrentMatchesList, DECLARATION_PARENT, DIRECTIVES, FLAGS, HEADER_OFFSET, HOST_NODE, INJECTOR, LViewData, LViewFlags, NEXT, PARENT, QUERIES, RENDERER, RootContext, SANITIZER, TAIL, TData, TVIEW, TView} from './interfaces/view';
|
||||
import {assertNodeOfPossibleTypes, assertNodeType} from './node_assert';
|
||||
import {appendChild, appendProjectedNode, canInsertNativeNode, createTextNode, findComponentHost, getChildLNode, getLViewChild, getNextLNode, getParentLNode, insertView, removeView} from './node_manipulation';
|
||||
import {isNodeMatchingSelectorList, matchingSelectorIndex} from './node_selector_matcher';
|
||||
@ -253,11 +253,13 @@ export function leaveView(newView: LViewData, creationOnly?: boolean): void {
|
||||
/**
|
||||
* Refreshes the view, executing the following steps in that order:
|
||||
* triggers init hooks, refreshes dynamic embedded views, triggers content hooks, sets host
|
||||
* bindings,
|
||||
* refreshes child components.
|
||||
* bindings, refreshes child components.
|
||||
* Note: view hooks are triggered later when leaving the view.
|
||||
*/
|
||||
function refreshView() {
|
||||
function refreshDescendantViews() {
|
||||
// This needs to be set before children are processed to support recursive components
|
||||
tView.firstTemplatePass = firstTemplatePass = false;
|
||||
|
||||
if (!checkNoChangesMode) {
|
||||
executeInitHooks(viewData, tView, creationMode);
|
||||
}
|
||||
@ -266,9 +268,6 @@ function refreshView() {
|
||||
executeHooks(directives !, tView.contentHooks, tView.contentCheckHooks, creationMode);
|
||||
}
|
||||
|
||||
// This needs to be set before children are processed to support recursive components
|
||||
tView.firstTemplatePass = firstTemplatePass = false;
|
||||
|
||||
setHostBindings(tView.hostBindings);
|
||||
refreshContentQueries(tView);
|
||||
refreshChildComponents(tView.components);
|
||||
@ -302,8 +301,8 @@ function refreshContentQueries(tView: TView): void {
|
||||
/** Refreshes child components in the current view. */
|
||||
function refreshChildComponents(components: number[] | null): void {
|
||||
if (components != null) {
|
||||
for (let i = 0; i < components.length; i += 2) {
|
||||
componentRefresh(components[i], components[i + 1]);
|
||||
for (let i = 0; i < components.length; i++) {
|
||||
componentRefresh(components[i]);
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -335,6 +334,7 @@ export function createLViewData<T>(
|
||||
null, // tail
|
||||
-1, // containerIndex
|
||||
null, // contentQueries
|
||||
null // declarationParent
|
||||
];
|
||||
}
|
||||
|
||||
@ -500,7 +500,8 @@ export function renderTemplate<T>(
|
||||
* Such lViewNode will then be renderer with renderEmbeddedTemplate() (see below).
|
||||
*/
|
||||
export function createEmbeddedViewNode<T>(
|
||||
tView: TView, context: T, renderer: Renderer3, queries?: LQueries | null): LViewNode {
|
||||
tView: TView, context: T, declarationParent: LViewData, renderer: Renderer3,
|
||||
queries?: LQueries | null): LViewNode {
|
||||
const _isParent = isParent;
|
||||
const _previousOrParentNode = previousOrParentNode;
|
||||
isParent = true;
|
||||
@ -508,6 +509,8 @@ export function createEmbeddedViewNode<T>(
|
||||
|
||||
const lView =
|
||||
createLViewData(renderer, tView, context, LViewFlags.CheckAlways, getCurrentSanitizer());
|
||||
lView[DECLARATION_PARENT] = declarationParent;
|
||||
|
||||
if (queries) {
|
||||
lView[QUERIES] = queries.createView();
|
||||
}
|
||||
@ -544,9 +547,9 @@ export function renderEmbeddedTemplate<T>(
|
||||
|
||||
oldView = enterView(viewNode.data !, viewNode);
|
||||
namespaceHTML();
|
||||
tView.template !(rf, context);
|
||||
callTemplateWithContexts(rf, context, tView.template !, viewNode.data ![DECLARATION_PARENT] !);
|
||||
if (rf & RenderFlags.Update) {
|
||||
refreshView();
|
||||
refreshDescendantViews();
|
||||
} else {
|
||||
viewNode.data ![TVIEW].firstTemplatePass = firstTemplatePass = false;
|
||||
}
|
||||
@ -562,6 +565,99 @@ export function renderEmbeddedTemplate<T>(
|
||||
return viewNode;
|
||||
}
|
||||
|
||||
/**
|
||||
* This function calls the template function of a dynamically created view with
|
||||
* all of its declaration parent contexts (up the view tree) until it reaches the
|
||||
* component boundary.
|
||||
*
|
||||
* Example:
|
||||
*
|
||||
* AppComponent template:
|
||||
* <ul *ngFor="let list of lists">
|
||||
* <li *ngFor="let item of list"> {{ item }} </li>
|
||||
* </ul>
|
||||
*
|
||||
* function AppComponentTemplate(rf, ctx) {
|
||||
* // instructions
|
||||
* function ulTemplate(rf, ulCtx, appCtx) {...}
|
||||
* function liTemplate(rf, liCtx, ulCtx, appCtx) {...}
|
||||
* }
|
||||
*
|
||||
* The ul view's template must be called with its own context and its declaration
|
||||
* parent, AppComponent. The li view's template must be called with its own context, its
|
||||
* parent (the ul), and the ul's parent (AppComponent).
|
||||
*
|
||||
* Note that a declaration parent is NOT always the same as the insertion parent. Templates
|
||||
* can be declared in different views than they are used.
|
||||
*
|
||||
* @param rf The RenderFlags for this template invocation
|
||||
* @param context The context for this template
|
||||
* @param template The template function to call
|
||||
* @param parent1 The declaration parent of the dynamic view
|
||||
*/
|
||||
function callTemplateWithContexts(
|
||||
rf: RenderFlags, context: any, template: ComponentTemplate<any>, parent1: LViewData): void {
|
||||
const parent2 = parent1[DECLARATION_PARENT];
|
||||
// Calling a function with extra arguments has a VM cost, so only call with necessary args
|
||||
if (!parent2) return template(rf, context, parent1[CONTEXT]);
|
||||
|
||||
const parent3 = parent2[DECLARATION_PARENT];
|
||||
if (!parent3) return template(rf, context, parent1[CONTEXT], parent2[CONTEXT]);
|
||||
|
||||
const parent4 = parent3[DECLARATION_PARENT];
|
||||
if (!parent4) {
|
||||
return template(rf, context, parent1[CONTEXT], parent2[CONTEXT], parent3[CONTEXT]);
|
||||
}
|
||||
|
||||
const parent5 = parent4[DECLARATION_PARENT];
|
||||
if (!parent5) {
|
||||
return template(
|
||||
rf, context, parent1[CONTEXT], parent2[CONTEXT], parent3[CONTEXT], parent4[CONTEXT]);
|
||||
}
|
||||
|
||||
const parent6 = parent5[DECLARATION_PARENT];
|
||||
if (!parent6) {
|
||||
return template(
|
||||
rf, context, parent1[CONTEXT], parent2[CONTEXT], parent3[CONTEXT], parent4[CONTEXT],
|
||||
parent5[CONTEXT]);
|
||||
}
|
||||
|
||||
const parent7 = parent6[DECLARATION_PARENT];
|
||||
if (!parent7) {
|
||||
return template(
|
||||
rf, context, parent1[CONTEXT], parent2[CONTEXT], parent3[CONTEXT], parent4[CONTEXT],
|
||||
parent5[CONTEXT], parent6[CONTEXT]);
|
||||
}
|
||||
|
||||
const parent8 = parent7[DECLARATION_PARENT];
|
||||
if (!parent8) {
|
||||
return template(
|
||||
rf, context, parent1[CONTEXT], parent2[CONTEXT], parent3[CONTEXT], parent4[CONTEXT],
|
||||
parent5[CONTEXT], parent6[CONTEXT], parent7[CONTEXT]);
|
||||
}
|
||||
|
||||
const parent9 = parent8[DECLARATION_PARENT];
|
||||
if (!parent9) {
|
||||
return template(
|
||||
rf, context, parent1[CONTEXT], parent2[CONTEXT], parent3[CONTEXT], parent4[CONTEXT],
|
||||
parent5[CONTEXT], parent6[CONTEXT], parent7[CONTEXT], parent8[CONTEXT]);
|
||||
}
|
||||
|
||||
// We support up to 8 nesting levels in embedded views before we give up and call apply()
|
||||
const contexts = [
|
||||
parent1[CONTEXT], parent2[CONTEXT], parent3[CONTEXT], parent4[CONTEXT], parent5[CONTEXT],
|
||||
parent6[CONTEXT], parent7[CONTEXT], parent8[CONTEXT], parent9[CONTEXT]
|
||||
];
|
||||
|
||||
let currentView: LViewData = parent9;
|
||||
while (currentView[DECLARATION_PARENT]) {
|
||||
contexts.push(currentView[DECLARATION_PARENT] ![CONTEXT]);
|
||||
currentView = currentView[DECLARATION_PARENT] !;
|
||||
}
|
||||
|
||||
tView.template !(rf, context, ...contexts);
|
||||
}
|
||||
|
||||
export function renderComponentOrTemplate<T>(
|
||||
node: LElementNode, hostView: LViewData, componentOrContext: T,
|
||||
template?: ComponentTemplate<T>) {
|
||||
@ -573,14 +669,14 @@ export function renderComponentOrTemplate<T>(
|
||||
if (template) {
|
||||
namespaceHTML();
|
||||
template(getRenderFlags(hostView), componentOrContext !);
|
||||
refreshView();
|
||||
refreshDescendantViews();
|
||||
} else {
|
||||
executeInitAndContentHooks();
|
||||
|
||||
// Element was stored at 0 in data and directive was stored at 0 in directives
|
||||
// in renderComponent()
|
||||
setHostBindings(_ROOT_DIRECTIVE_INDICES);
|
||||
componentRefresh(0, HEADER_OFFSET);
|
||||
componentRefresh(HEADER_OFFSET);
|
||||
}
|
||||
} finally {
|
||||
if (rendererFactory.end) {
|
||||
@ -770,9 +866,9 @@ export function resolveDirective(
|
||||
}
|
||||
|
||||
/** Stores index of component's host element so it will be queued for view refresh during CD. */
|
||||
function queueComponentIndexForCheck(dirIndex: number): void {
|
||||
function queueComponentIndexForCheck(): void {
|
||||
if (firstTemplatePass) {
|
||||
(tView.components || (tView.components = [])).push(dirIndex, viewData.length - 1);
|
||||
(tView.components || (tView.components = [])).push(viewData.length - 1);
|
||||
}
|
||||
}
|
||||
|
||||
@ -1543,7 +1639,7 @@ function addComponentLogic<T>(
|
||||
viewData, previousOrParentNode.tNode.index as number,
|
||||
createLViewData(
|
||||
rendererFactory.createRenderer(previousOrParentNode.native as RElement, def.rendererType),
|
||||
tView, null, def.onPush ? LViewFlags.Dirty : LViewFlags.CheckAlways,
|
||||
tView, instance, def.onPush ? LViewFlags.Dirty : LViewFlags.CheckAlways,
|
||||
getCurrentSanitizer()));
|
||||
|
||||
// We need to set the host node/data here because when the component LNode was created,
|
||||
@ -1553,7 +1649,7 @@ function addComponentLogic<T>(
|
||||
|
||||
initChangeDetectorIfExisting(previousOrParentNode.nodeInjector, instance, componentView);
|
||||
|
||||
if (firstTemplatePass) queueComponentIndexForCheck(directiveIndex);
|
||||
if (firstTemplatePass) queueComponentIndexForCheck();
|
||||
}
|
||||
|
||||
/**
|
||||
@ -1914,7 +2010,7 @@ function getOrCreateEmbeddedTView(viewIndex: number, parent: LContainerNode): TV
|
||||
|
||||
/** Marks the end of an embedded view. */
|
||||
export function embeddedViewEnd(): void {
|
||||
refreshView();
|
||||
refreshDescendantViews();
|
||||
isParent = false;
|
||||
previousOrParentNode = viewData[HOST_NODE] as LViewNode;
|
||||
leaveView(viewData[PARENT] !);
|
||||
@ -1927,10 +2023,9 @@ export function embeddedViewEnd(): void {
|
||||
/**
|
||||
* Refreshes components by entering the component view and processing its bindings, queries, etc.
|
||||
*
|
||||
* @param directiveIndex Directive index in LViewData[DIRECTIVES]
|
||||
* @param adjustedElementIndex Element index in LViewData[] (adjusted for HEADER_OFFSET)
|
||||
*/
|
||||
export function componentRefresh<T>(directiveIndex: number, adjustedElementIndex: number): void {
|
||||
export function componentRefresh<T>(adjustedElementIndex: number): void {
|
||||
ngDevMode && assertDataInRange(adjustedElementIndex);
|
||||
const element = viewData[adjustedElementIndex] as LElementNode;
|
||||
ngDevMode && assertNodeType(element, TNodeType.Element);
|
||||
@ -1940,8 +2035,7 @@ export function componentRefresh<T>(directiveIndex: number, adjustedElementIndex
|
||||
|
||||
// Only attached CheckAlways components or attached, dirty OnPush components should be checked
|
||||
if (viewAttached(hostView) && hostView[FLAGS] & (LViewFlags.CheckAlways | LViewFlags.Dirty)) {
|
||||
ngDevMode && assertDataInRange(directiveIndex, directives !);
|
||||
detectChangesInternal(hostView, element, directives ![directiveIndex]);
|
||||
detectChangesInternal(hostView, element, hostView[CONTEXT]);
|
||||
}
|
||||
}
|
||||
|
||||
@ -2267,7 +2361,7 @@ export function detectChangesInternal<T>(
|
||||
namespaceHTML();
|
||||
createViewQuery(viewQuery, hostView[FLAGS], component);
|
||||
template(getRenderFlags(hostView), component);
|
||||
refreshView();
|
||||
refreshDescendantViews();
|
||||
updateViewQuery(viewQuery, component);
|
||||
} finally {
|
||||
leaveView(oldView);
|
||||
|
@ -15,7 +15,7 @@ import {CssSelectorList} from './projection';
|
||||
* Definition of what a template rendering function should look like.
|
||||
*/
|
||||
export type ComponentTemplate<T> = {
|
||||
(rf: RenderFlags, ctx: T): void; ngPrivateData?: never;
|
||||
(rf: RenderFlags, ctx: T, ...parentCtx: ({} | null)[]): void; ngPrivateData?: never;
|
||||
};
|
||||
|
||||
/**
|
||||
|
@ -17,7 +17,7 @@ import {LQueries} from './query';
|
||||
import {Renderer3} from './renderer';
|
||||
|
||||
/** Size of LViewData's header. Necessary to adjust for it when setting slots. */
|
||||
export const HEADER_OFFSET = 16;
|
||||
export const HEADER_OFFSET = 17;
|
||||
|
||||
// Below are constants for LViewData indices to help us look up LViewData members
|
||||
// without having to remember the specific indices.
|
||||
@ -38,6 +38,7 @@ export const SANITIZER = 12;
|
||||
export const TAIL = 13;
|
||||
export const CONTAINER_INDEX = 14;
|
||||
export const CONTENT_QUERIES = 15;
|
||||
export const DECLARATION_PARENT = 16;
|
||||
|
||||
/**
|
||||
* `LViewData` stores all of the information needed to process the instructions as
|
||||
@ -61,6 +62,9 @@ export interface LViewData extends Array<any> {
|
||||
* The parent view is needed when we exit the view and must restore the previous
|
||||
* `LViewData`. Without this, the render method would have to keep a stack of
|
||||
* views as it is recursively rendering templates.
|
||||
*
|
||||
* This is also the "insertion" parent for embedded views. This allows us to properly
|
||||
* destroy embedded views.
|
||||
*/
|
||||
[PARENT]: LViewData|null;
|
||||
|
||||
@ -143,7 +147,6 @@ export interface LViewData extends Array<any> {
|
||||
* The tail allows us to quickly add a new state to the end of the view list
|
||||
* without having to propagate starting from the first child.
|
||||
*/
|
||||
// TODO: replace with global
|
||||
[TAIL]: LViewData|LContainer|null;
|
||||
|
||||
/**
|
||||
@ -162,6 +165,32 @@ export interface LViewData extends Array<any> {
|
||||
* be refreshed.
|
||||
*/
|
||||
[CONTENT_QUERIES]: QueryList<any>[]|null;
|
||||
|
||||
/**
|
||||
* Parent view where this view's template was declared.
|
||||
*
|
||||
* Only applicable for dynamically created views. Will be null for inline/component views.
|
||||
*
|
||||
* The template for a dynamically created view may be declared in a different view than
|
||||
* it is inserted. We already track the "insertion parent" (view where the template was
|
||||
* inserted) in LViewData[PARENT], but we also need access to the "declaration parent"
|
||||
* (view where the template was declared). Otherwise, we wouldn't be able to call the
|
||||
* view's template function with the proper contexts. Context should be inherited from
|
||||
* the declaration parent tree, not the insertion parent tree.
|
||||
*
|
||||
* Example (AppComponent template):
|
||||
*
|
||||
* <ng-template #foo></ng-template> <-- declared here -->
|
||||
* <some-comp [tpl]="foo"></some-comp> <-- inserted inside this component -->
|
||||
*
|
||||
* The <ng-template> above is declared in the AppComponent template, but it will be passed into
|
||||
* SomeComp and inserted there. In this case, the declaration parent would be the AppComponent,
|
||||
* but the insertion parent would be SomeComp. When we are removing views, we would want to
|
||||
* traverse through the insertion parent to clean up listeners. When we are calling the
|
||||
* template function during change detection, we need the declaration parent to get inherited
|
||||
* context.
|
||||
*/
|
||||
[DECLARATION_PARENT]: LViewData|null;
|
||||
}
|
||||
|
||||
/** Flags associated with an LView (saved in LViewData[FLAGS]) */
|
||||
@ -404,11 +433,10 @@ export interface TView {
|
||||
cleanup: any[]|null;
|
||||
|
||||
/**
|
||||
* A list of directive and element indices for child components that will need to be
|
||||
* refreshed when the current view has finished its check.
|
||||
* A list of element indices for child components that will need to be
|
||||
* refreshed when the current view has finished its check. These indices have
|
||||
* already been adjusted for the HEADER_OFFSET.
|
||||
*
|
||||
* Even indices: Directive indices
|
||||
* Odd indices: Element indices (adjusted for LViewData header offset)
|
||||
*/
|
||||
components: number[]|null;
|
||||
|
||||
|
Reference in New Issue
Block a user