fix(ivy): flatten template fns for nested views (#24943)
PR Close #24943
This commit is contained in:
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;
|
||||
|
||||
|
|
|
@ -10,7 +10,7 @@ import {NgForOfContext} from '@angular/common';
|
|||
|
||||
import {getOrCreateNodeInjectorForNode, getOrCreateTemplateRef} from '../../src/render3/di';
|
||||
import {AttributeMarker, defineComponent} from '../../src/render3/index';
|
||||
import {bind, container, elementEnd, elementProperty, elementStart, interpolation1, interpolation3, listener, load, text, textBinding} from '../../src/render3/instructions';
|
||||
import {bind, container, elementEnd, elementProperty, elementStart, interpolation1, interpolation2, interpolation3, interpolationV, listener, load, text, textBinding} from '../../src/render3/instructions';
|
||||
import {RenderFlags} from '../../src/render3/interfaces/definition';
|
||||
|
||||
import {NgForOf, NgIf, NgTemplateOutlet} from './common_with_def';
|
||||
|
@ -40,7 +40,7 @@ describe('@angular/common integration', () => {
|
|||
elementProperty(1, 'ngForOf', bind(myApp.items));
|
||||
}
|
||||
|
||||
function liTemplate(rf1: RenderFlags, row: NgForOfContext<string>) {
|
||||
function liTemplate(rf1: RenderFlags, row: NgForOfContext<string>, parent: MyApp) {
|
||||
if (rf1 & RenderFlags.Create) {
|
||||
elementStart(0, 'li');
|
||||
{ text(1); }
|
||||
|
@ -100,7 +100,7 @@ describe('@angular/common integration', () => {
|
|||
elementProperty(1, 'ngForOf', bind(myApp.items));
|
||||
}
|
||||
|
||||
function liTemplate(rf1: RenderFlags, row: NgForOfContext<string>) {
|
||||
function liTemplate(rf1: RenderFlags, row: NgForOfContext<string>, parent: MyApp) {
|
||||
if (rf1 & RenderFlags.Create) {
|
||||
elementStart(0, 'li');
|
||||
{ text(1); }
|
||||
|
@ -164,7 +164,7 @@ describe('@angular/common integration', () => {
|
|||
elementProperty(3, 'ngForOf', bind(myApp.items));
|
||||
}
|
||||
|
||||
function liTemplate(rf1: RenderFlags, row: NgForOfContext<string>) {
|
||||
function liTemplate(rf1: RenderFlags, row: NgForOfContext<string>, parent: MyApp) {
|
||||
if (rf1 & RenderFlags.Create) {
|
||||
elementStart(0, 'li');
|
||||
{ text(1); }
|
||||
|
@ -203,14 +203,14 @@ describe('@angular/common integration', () => {
|
|||
|
||||
it('should support multiple levels of embedded templates', () => {
|
||||
/**
|
||||
* <ul *ngFor="let outterItem of items.">
|
||||
* <li *ngFor="let item of items">
|
||||
* <span>{{item}}</span>
|
||||
* <ul>
|
||||
* <li *ngFor="let row of items">
|
||||
* <span *ngFor="let cell of row.data">{{cell}} - {{ row.value }}</span>
|
||||
* </li>
|
||||
* </ul>
|
||||
*/
|
||||
class MyApp {
|
||||
items: string[] = ['1', '2'];
|
||||
items: any[] = [{data: ['1', '2'], value: 'first'}, {data: ['3', '4'], value: 'second'}];
|
||||
|
||||
static ngComponentDef = defineComponent({
|
||||
type: MyApp,
|
||||
|
@ -226,25 +226,27 @@ describe('@angular/common integration', () => {
|
|||
elementProperty(1, 'ngForOf', bind(myApp.items));
|
||||
}
|
||||
|
||||
function liTemplate(rf1: RenderFlags, row: NgForOfContext<string>) {
|
||||
function liTemplate(rf1: RenderFlags, row: any, myApp: MyApp) {
|
||||
if (rf1 & RenderFlags.Create) {
|
||||
elementStart(0, 'li');
|
||||
{ container(1, spanTemplate, null, ['ngForOf', '']); }
|
||||
elementEnd();
|
||||
}
|
||||
if (rf1 & RenderFlags.Update) {
|
||||
elementProperty(1, 'ngForOf', bind(myApp.items));
|
||||
const r1 = row.$implicit as any;
|
||||
elementProperty(1, 'ngForOf', bind(r1.data));
|
||||
}
|
||||
}
|
||||
|
||||
function spanTemplate(rf1: RenderFlags, row: NgForOfContext<string>) {
|
||||
function spanTemplate(rf1: RenderFlags, cell: any, row: any, myApp: MyApp) {
|
||||
if (rf1 & RenderFlags.Create) {
|
||||
elementStart(0, 'span');
|
||||
{ text(1); }
|
||||
elementEnd();
|
||||
}
|
||||
if (rf1 & RenderFlags.Update) {
|
||||
textBinding(1, bind(row.$implicit));
|
||||
textBinding(
|
||||
1, interpolation2('', cell.$implicit, ' - ', (row.$implicit as any).value, ''));
|
||||
}
|
||||
}
|
||||
},
|
||||
|
@ -258,25 +260,283 @@ describe('@angular/common integration', () => {
|
|||
fixture.update();
|
||||
expect(fixture.html)
|
||||
.toEqual(
|
||||
'<ul><li><span>1</span><span>2</span></li><li><span>1</span><span>2</span></li></ul>');
|
||||
'<ul><li><span>1 - first</span><span>2 - first</span></li><li><span>3 - second</span><span>4 - second</span></li></ul>');
|
||||
|
||||
// Remove the last item
|
||||
fixture.component.items.length = 1;
|
||||
fixture.update();
|
||||
expect(fixture.html).toEqual('<ul><li><span>1</span></li></ul>');
|
||||
expect(fixture.html)
|
||||
.toEqual('<ul><li><span>1 - first</span><span>2 - first</span></li></ul>');
|
||||
|
||||
// Change an item
|
||||
fixture.component.items[0] = 'one';
|
||||
fixture.component.items[0].data[0] = 'one';
|
||||
fixture.update();
|
||||
expect(fixture.html).toEqual('<ul><li><span>one</span></li></ul>');
|
||||
expect(fixture.html)
|
||||
.toEqual('<ul><li><span>one - first</span><span>2 - first</span></li></ul>');
|
||||
|
||||
// Add an item
|
||||
fixture.component.items.push('two');
|
||||
fixture.component.items[1] = {data: ['three', '4'], value: 'third'};
|
||||
fixture.update();
|
||||
expect(fixture.html)
|
||||
.toEqual(
|
||||
'<ul><li><span>one</span><span>two</span></li><li><span>one</span><span>two</span></li></ul>');
|
||||
'<ul><li><span>one - first</span><span>2 - first</span></li><li><span>three - third</span><span>4 - third</span></li></ul>');
|
||||
});
|
||||
|
||||
it('should support context for 9+ levels of embedded templates', () => {
|
||||
/**
|
||||
*
|
||||
* <span *ngFor="let item0 of items">
|
||||
* <span *ngFor="let item1 of item0.data">
|
||||
* <span *ngFor="let item2 of item1.data">
|
||||
* <span *ngFor="let item3 of item2.data">
|
||||
* <span *ngFor="let item4 of item3.data">
|
||||
* <span *ngFor="let item5 of item4.data">
|
||||
* <span *ngFor="let item6 of item5.data">
|
||||
* <span *ngFor="let item7 of item6.data">
|
||||
* <span *ngFor="let item8 of item7.data">
|
||||
* {{ item8 }} - {{ item7.value }} - {{ item6.value }}...
|
||||
* </span>
|
||||
* </span>
|
||||
* </span>
|
||||
* </span>
|
||||
* </span>
|
||||
* </span>
|
||||
* </span>
|
||||
* </span>
|
||||
* </span>
|
||||
*/
|
||||
class MyApp {
|
||||
value = 'App';
|
||||
items: any[] = [
|
||||
{
|
||||
// item0
|
||||
data: [{
|
||||
// item1
|
||||
data: [{
|
||||
// item2
|
||||
data: [{
|
||||
// item3
|
||||
data: [{
|
||||
// item4
|
||||
data: [{
|
||||
// item5
|
||||
data: [{
|
||||
// item6
|
||||
data: [{
|
||||
// item7
|
||||
data: [
|
||||
'1', '2' // item8
|
||||
],
|
||||
value: 'h'
|
||||
}],
|
||||
value: 'g'
|
||||
}],
|
||||
value: 'f'
|
||||
}],
|
||||
value: 'e'
|
||||
}],
|
||||
value: 'd'
|
||||
}],
|
||||
value: 'c'
|
||||
}],
|
||||
value: 'b'
|
||||
}],
|
||||
value: 'a'
|
||||
},
|
||||
{
|
||||
// item0
|
||||
data: [{
|
||||
// item1
|
||||
data: [{
|
||||
// item2
|
||||
data: [{
|
||||
// item3
|
||||
data: [{
|
||||
// item4
|
||||
data: [{
|
||||
// item5
|
||||
data: [{
|
||||
// item6
|
||||
data: [{
|
||||
// item7
|
||||
data: [
|
||||
'3', '4' // item8
|
||||
],
|
||||
value: 'H'
|
||||
}],
|
||||
value: 'G'
|
||||
}],
|
||||
value: 'F'
|
||||
}],
|
||||
value: 'E'
|
||||
}],
|
||||
value: 'D'
|
||||
}],
|
||||
value: 'C'
|
||||
}],
|
||||
value: 'B'
|
||||
}],
|
||||
value: 'A'
|
||||
}
|
||||
];
|
||||
|
||||
static ngComponentDef = defineComponent({
|
||||
type: MyApp,
|
||||
factory: () => new MyApp(),
|
||||
selectors: [['my-app']],
|
||||
template: (rf: RenderFlags, myApp: MyApp) => {
|
||||
if (rf & RenderFlags.Create) {
|
||||
container(0, itemTemplate0, null, ['ngForOf', '']);
|
||||
}
|
||||
if (rf & RenderFlags.Update) {
|
||||
elementProperty(0, 'ngForOf', bind(myApp.items));
|
||||
}
|
||||
|
||||
function itemTemplate0(rf1: RenderFlags, item0: any, myApp: MyApp) {
|
||||
if (rf1 & RenderFlags.Create) {
|
||||
elementStart(0, 'span');
|
||||
{ container(1, itemTemplate1, null, ['ngForOf', '']); }
|
||||
elementEnd();
|
||||
}
|
||||
if (rf1 & RenderFlags.Update) {
|
||||
const item = item0.$implicit as any;
|
||||
elementProperty(1, 'ngForOf', bind(item.data));
|
||||
}
|
||||
}
|
||||
|
||||
function itemTemplate1(rf1: RenderFlags, item1: any, item0: any, myApp: MyApp) {
|
||||
if (rf1 & RenderFlags.Create) {
|
||||
elementStart(0, 'span');
|
||||
{ container(1, itemTemplate2, null, ['ngForOf', '']); }
|
||||
elementEnd();
|
||||
}
|
||||
if (rf1 & RenderFlags.Update) {
|
||||
const item = item1.$implicit as any;
|
||||
elementProperty(1, 'ngForOf', bind(item.data));
|
||||
}
|
||||
}
|
||||
|
||||
function itemTemplate2(
|
||||
rf1: RenderFlags, item2: any, item1: any, item0: any, myApp: MyApp) {
|
||||
if (rf1 & RenderFlags.Create) {
|
||||
elementStart(0, 'span');
|
||||
{ container(1, itemTemplate3, null, ['ngForOf', '']); }
|
||||
elementEnd();
|
||||
}
|
||||
if (rf1 & RenderFlags.Update) {
|
||||
const item = item2.$implicit as any;
|
||||
elementProperty(1, 'ngForOf', bind(item.data));
|
||||
}
|
||||
}
|
||||
|
||||
function itemTemplate3(
|
||||
rf1: RenderFlags, item3: any, item2: any, item1: any, item0: any, myApp: MyApp) {
|
||||
if (rf1 & RenderFlags.Create) {
|
||||
elementStart(0, 'span');
|
||||
{ container(1, itemTemplate4, null, ['ngForOf', '']); }
|
||||
elementEnd();
|
||||
}
|
||||
if (rf1 & RenderFlags.Update) {
|
||||
const item = item3.$implicit as any;
|
||||
elementProperty(1, 'ngForOf', bind(item.data));
|
||||
}
|
||||
}
|
||||
|
||||
function itemTemplate4(
|
||||
rf1: RenderFlags, item4: any, item3: any, item2: any, item1: any, item0: any,
|
||||
myApp: MyApp) {
|
||||
if (rf1 & RenderFlags.Create) {
|
||||
elementStart(0, 'span');
|
||||
{ container(1, itemTemplate5, null, ['ngForOf', '']); }
|
||||
elementEnd();
|
||||
}
|
||||
if (rf1 & RenderFlags.Update) {
|
||||
const item = item4.$implicit as any;
|
||||
elementProperty(1, 'ngForOf', bind(item.data));
|
||||
}
|
||||
}
|
||||
|
||||
function itemTemplate5(
|
||||
rf1: RenderFlags, item5: any, item4: any, item3: any, item2: any, item1: any,
|
||||
item0: any, myApp: MyApp) {
|
||||
if (rf1 & RenderFlags.Create) {
|
||||
elementStart(0, 'span');
|
||||
{ container(1, itemTemplate6, null, ['ngForOf', '']); }
|
||||
elementEnd();
|
||||
}
|
||||
if (rf1 & RenderFlags.Update) {
|
||||
const item = item5.$implicit as any;
|
||||
elementProperty(1, 'ngForOf', bind(item.data));
|
||||
}
|
||||
}
|
||||
|
||||
function itemTemplate6(
|
||||
rf1: RenderFlags, item6: any, item5: any, item4: any, item3: any, item2: any,
|
||||
item1: any, item0: any, myApp: MyApp) {
|
||||
if (rf1 & RenderFlags.Create) {
|
||||
elementStart(0, 'span');
|
||||
{ container(1, itemTemplate7, null, ['ngForOf', '']); }
|
||||
elementEnd();
|
||||
}
|
||||
if (rf1 & RenderFlags.Update) {
|
||||
const item = item6.$implicit as any;
|
||||
elementProperty(1, 'ngForOf', bind(item.data));
|
||||
}
|
||||
}
|
||||
|
||||
function itemTemplate7(
|
||||
rf1: RenderFlags, item7: any, item6: any, item5: any, item4: any, item3: any,
|
||||
item2: any, item1: any, item0: any, myApp: MyApp) {
|
||||
if (rf1 & RenderFlags.Create) {
|
||||
elementStart(0, 'span');
|
||||
{ container(1, itemTemplate8, null, ['ngForOf', '']); }
|
||||
elementEnd();
|
||||
}
|
||||
if (rf1 & RenderFlags.Update) {
|
||||
const item = item7.$implicit as any;
|
||||
elementProperty(1, 'ngForOf', bind(item.data));
|
||||
}
|
||||
}
|
||||
|
||||
function itemTemplate8(
|
||||
rf1: RenderFlags, item8: any, item7: any, item6: any, item5: any, item4: any,
|
||||
item3: any, item2: any, item1: any, item0: any, myApp: MyApp) {
|
||||
if (rf1 & RenderFlags.Create) {
|
||||
elementStart(0, 'span');
|
||||
{ text(1); }
|
||||
elementEnd();
|
||||
}
|
||||
|
||||
if (rf1 & RenderFlags.Update) {
|
||||
textBinding(1, interpolationV([
|
||||
'', item8.$implicit, '.', item7.$implicit.value,
|
||||
'.', item6.$implicit.value, '.', item5.$implicit.value,
|
||||
'.', item4.$implicit.value, '.', item3.$implicit.value,
|
||||
'.', item2.$implicit.value, '.', item1.$implicit.value,
|
||||
'.', item0.$implicit.value, '.', myApp.value,
|
||||
''
|
||||
]));
|
||||
}
|
||||
}
|
||||
},
|
||||
directives: () => [NgForOf]
|
||||
});
|
||||
}
|
||||
|
||||
const fixture = new ComponentFixture(MyApp);
|
||||
|
||||
expect(fixture.html)
|
||||
.toEqual(
|
||||
'<span><span><span><span><span><span><span><span>' +
|
||||
'<span>1.h.g.f.e.d.c.b.a.App</span>' +
|
||||
'<span>2.h.g.f.e.d.c.b.a.App</span>' +
|
||||
'</span></span></span></span></span></span></span></span>' +
|
||||
'<span><span><span><span><span><span><span><span>' +
|
||||
'<span>3.H.G.F.E.D.C.B.A.App</span>' +
|
||||
'<span>4.H.G.F.E.D.C.B.A.App</span>' +
|
||||
'</span></span></span></span></span></span></span></span>');
|
||||
});
|
||||
|
||||
});
|
||||
|
||||
describe('ngIf', () => {
|
||||
|
@ -304,7 +564,7 @@ describe('@angular/common integration', () => {
|
|||
elementProperty(1, 'ngIf', bind(myApp.showing));
|
||||
}
|
||||
|
||||
function templateOne(rf: RenderFlags, ctx: any) {
|
||||
function templateOne(rf: RenderFlags, ctx: any, parent: MyApp) {
|
||||
if (rf & RenderFlags.Create) {
|
||||
elementStart(0, 'div');
|
||||
{ text(1); }
|
||||
|
@ -314,7 +574,7 @@ describe('@angular/common integration', () => {
|
|||
textBinding(1, bind(myApp.valueOne));
|
||||
}
|
||||
}
|
||||
function templateTwo(rf: RenderFlags, ctx: any) {
|
||||
function templateTwo(rf: RenderFlags, ctx: any, parent: MyApp) {
|
||||
if (rf & RenderFlags.Create) {
|
||||
elementStart(0, 'div');
|
||||
{ text(1); }
|
||||
|
|
|
@ -7,15 +7,16 @@
|
|||
*/
|
||||
|
||||
|
||||
import {DoCheck, ViewEncapsulation, createInjector, defineInjectable, defineInjector} from '../../src/core';
|
||||
import {DoCheck, Input, TemplateRef, ViewContainerRef, ViewEncapsulation, createInjector, defineInjectable, defineInjector} from '../../src/core';
|
||||
import {getRenderedText} from '../../src/render3/component';
|
||||
import {ComponentFactory, LifecycleHooksFeature, defineComponent, directiveInject, markDirty} from '../../src/render3/index';
|
||||
import {AttributeMarker, ComponentFactory, LifecycleHooksFeature, defineComponent, directiveInject, markDirty} from '../../src/render3/index';
|
||||
import {bind, container, containerRefreshEnd, containerRefreshStart, element, elementEnd, elementProperty, elementStart, embeddedViewEnd, embeddedViewStart, text, textBinding, tick} from '../../src/render3/instructions';
|
||||
import {ComponentDefInternal, DirectiveDefInternal, RenderFlags} from '../../src/render3/interfaces/definition';
|
||||
import {createRendererType2} from '../../src/view/index';
|
||||
|
||||
import {NgIf} from './common_with_def';
|
||||
import {getRendererFactory2} from './imported_renderer2';
|
||||
import {ComponentFixture, containerEl, renderComponent, renderToHtml, requestAnimationFrame, toHtml} from './render_util';
|
||||
import {ComponentFixture, containerEl, createComponent, renderComponent, renderToHtml, requestAnimationFrame, toHtml} from './render_util';
|
||||
|
||||
describe('component', () => {
|
||||
class CounterComponent {
|
||||
|
@ -281,8 +282,13 @@ describe('encapsulation', () => {
|
|||
});
|
||||
|
||||
describe('recursive components', () => {
|
||||
let events: string[] = [];
|
||||
let count = 0;
|
||||
let events: string[];
|
||||
let count: number;
|
||||
|
||||
beforeEach(() => {
|
||||
events = [];
|
||||
count = 0;
|
||||
});
|
||||
|
||||
class TreeNode {
|
||||
constructor(
|
||||
|
@ -290,11 +296,23 @@ describe('recursive components', () => {
|
|||
public right: TreeNode|null) {}
|
||||
}
|
||||
|
||||
/**
|
||||
* {{ data.value }}
|
||||
*
|
||||
* % if (data.left != null) {
|
||||
* <tree-comp [data]="data.left"></tree-comp>
|
||||
* % }
|
||||
* % if (data.right != null) {
|
||||
* <tree-comp [data]="data.right"></tree-comp>
|
||||
* % }
|
||||
*/
|
||||
class TreeComponent {
|
||||
data: TreeNode = _buildTree(0);
|
||||
|
||||
ngDoCheck() { events.push('check' + this.data.value); }
|
||||
|
||||
ngOnDestroy() { events.push('destroy' + this.data.value); }
|
||||
|
||||
static ngComponentDef = defineComponent({
|
||||
type: TreeComponent,
|
||||
selectors: [['tree-comp']],
|
||||
|
@ -344,6 +362,58 @@ describe('recursive components', () => {
|
|||
(TreeComponent.ngComponentDef as ComponentDefInternal<TreeComponent>).directiveDefs =
|
||||
() => [TreeComponent.ngComponentDef];
|
||||
|
||||
/**
|
||||
* {{ data.value }}
|
||||
* <ng-if-tree [data]="data.left" *ngIf="data.left"></ng-if-tree>
|
||||
* <ng-if-tree [data]="data.right" *ngIf="data.right"></ng-if-tree>
|
||||
*/
|
||||
class NgIfTree {
|
||||
data: TreeNode = _buildTree(0);
|
||||
|
||||
ngOnDestroy() { events.push('destroy' + this.data.value); }
|
||||
|
||||
static ngComponentDef = defineComponent({
|
||||
type: NgIfTree,
|
||||
selectors: [['ng-if-tree']],
|
||||
factory: () => new NgIfTree(),
|
||||
template: (rf: RenderFlags, ctx: NgIfTree) => {
|
||||
|
||||
if (rf & RenderFlags.Create) {
|
||||
text(0);
|
||||
container(1, IfTemplate, '', [AttributeMarker.SelectOnly, 'ngIf']);
|
||||
container(2, IfTemplate2, '', [AttributeMarker.SelectOnly, 'ngIf']);
|
||||
}
|
||||
if (rf & RenderFlags.Update) {
|
||||
textBinding(0, bind(ctx.data.value));
|
||||
elementProperty(1, 'ngIf', bind(ctx.data.left));
|
||||
elementProperty(2, 'ngIf', bind(ctx.data.right));
|
||||
}
|
||||
|
||||
function IfTemplate(rf1: RenderFlags, left: any, parent: NgIfTree) {
|
||||
if (rf1 & RenderFlags.Create) {
|
||||
elementStart(0, 'ng-if-tree');
|
||||
elementEnd();
|
||||
}
|
||||
if (rf1 & RenderFlags.Update) {
|
||||
elementProperty(0, 'data', bind(parent.data.left));
|
||||
}
|
||||
}
|
||||
function IfTemplate2(rf1: RenderFlags, right: any, parent: NgIfTree) {
|
||||
if (rf1 & RenderFlags.Create) {
|
||||
elementStart(0, 'ng-if-tree');
|
||||
elementEnd();
|
||||
}
|
||||
if (rf1 & RenderFlags.Update) {
|
||||
elementProperty(0, 'data', bind(parent.data.right));
|
||||
}
|
||||
}
|
||||
},
|
||||
inputs: {data: 'data'},
|
||||
});
|
||||
}
|
||||
(NgIfTree.ngComponentDef as ComponentDefInternal<NgIfTree>).directiveDefs =
|
||||
() => [NgIfTree.ngComponentDef, NgIf.ngDirectiveDef];
|
||||
|
||||
function _buildTree(currDepth: number): TreeNode {
|
||||
const children = currDepth < 2 ? _buildTree(currDepth + 1) : null;
|
||||
const children2 = currDepth < 2 ? _buildTree(currDepth + 1) : null;
|
||||
|
@ -360,6 +430,76 @@ describe('recursive components', () => {
|
|||
expect(events).toEqual(['check6', 'check2', 'check0', 'check1', 'check5', 'check3', 'check4']);
|
||||
});
|
||||
|
||||
// This tests that the view tree is set up properly for recursive components
|
||||
it('should call onDestroys properly', () => {
|
||||
|
||||
/**
|
||||
* % if (!skipContent) {
|
||||
* <tree-comp></tree-comp>
|
||||
* % }
|
||||
*/
|
||||
const App = createComponent('app', function(rf: RenderFlags, ctx: any) {
|
||||
if (rf & RenderFlags.Create) {
|
||||
container(0);
|
||||
}
|
||||
if (rf & RenderFlags.Update) {
|
||||
containerRefreshStart(0);
|
||||
if (!ctx.skipContent) {
|
||||
const rf0 = embeddedViewStart(0);
|
||||
if (rf0 & RenderFlags.Create) {
|
||||
elementStart(0, 'tree-comp');
|
||||
elementEnd();
|
||||
}
|
||||
embeddedViewEnd();
|
||||
}
|
||||
containerRefreshEnd();
|
||||
}
|
||||
}, [TreeComponent]);
|
||||
|
||||
const fixture = new ComponentFixture(App);
|
||||
expect(getRenderedText(fixture.component)).toEqual('6201534');
|
||||
|
||||
events = [];
|
||||
fixture.component.skipContent = true;
|
||||
fixture.update();
|
||||
expect(events).toEqual(
|
||||
['destroy0', 'destroy1', 'destroy2', 'destroy3', 'destroy4', 'destroy5', 'destroy6']);
|
||||
});
|
||||
|
||||
it('should call onDestroys properly with ngIf', () => {
|
||||
/**
|
||||
* % if (!skipContent) {
|
||||
* <ng-if-tree></ng-if-tree>
|
||||
* % }
|
||||
*/
|
||||
const App = createComponent('app', function(rf: RenderFlags, ctx: any) {
|
||||
if (rf & RenderFlags.Create) {
|
||||
container(0);
|
||||
}
|
||||
if (rf & RenderFlags.Update) {
|
||||
containerRefreshStart(0);
|
||||
if (!ctx.skipContent) {
|
||||
const rf0 = embeddedViewStart(0);
|
||||
if (rf0 & RenderFlags.Create) {
|
||||
elementStart(0, 'ng-if-tree');
|
||||
elementEnd();
|
||||
}
|
||||
embeddedViewEnd();
|
||||
}
|
||||
containerRefreshEnd();
|
||||
}
|
||||
}, [NgIfTree]);
|
||||
|
||||
const fixture = new ComponentFixture(App);
|
||||
expect(getRenderedText(fixture.component)).toEqual('6201534');
|
||||
|
||||
events = [];
|
||||
fixture.component.skipContent = true;
|
||||
fixture.update();
|
||||
expect(events).toEqual(
|
||||
['destroy0', 'destroy1', 'destroy2', 'destroy3', 'destroy4', 'destroy5', 'destroy6']);
|
||||
});
|
||||
|
||||
it('should map inputs minified & unminified names', async() => {
|
||||
class TestInputsComponent {
|
||||
// TODO(issue/24571): remove '!'.
|
||||
|
|
|
@ -15,6 +15,7 @@ import {AttributeMarker, detectChanges} from '../../src/render3/index';
|
|||
import {bind, container, containerRefreshEnd, containerRefreshStart, element, elementEnd, elementProperty, elementStart, embeddedViewEnd, embeddedViewStart, loadDirective, projection, projectionDef, text} from '../../src/render3/instructions';
|
||||
import {RenderFlags} from '../../src/render3/interfaces/definition';
|
||||
|
||||
import {NgIf} from './common_with_def';
|
||||
import {ComponentFixture, createComponent, renderComponent, toHtml} from './render_util';
|
||||
|
||||
describe('content projection', () => {
|
||||
|
@ -804,22 +805,6 @@ describe('content projection', () => {
|
|||
});
|
||||
|
||||
it('should project into dynamic views (with createEmbeddedView)', () => {
|
||||
class NgIf {
|
||||
constructor(public vcr: ViewContainerRef, public template: TemplateRef<any>) {}
|
||||
|
||||
@Input()
|
||||
set ngIf(value: boolean) {
|
||||
value ? this.vcr.createEmbeddedView(this.template) : this.vcr.clear();
|
||||
}
|
||||
|
||||
static ngDirectiveDef = defineDirective({
|
||||
type: NgIf,
|
||||
selectors: [['', 'ngIf', '']],
|
||||
inputs: {'ngIf': 'ngIf'},
|
||||
factory: () => new NgIf(injectViewContainerRef(), injectTemplateRef())
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* Before-
|
||||
* <ng-template [ngIf]="showing">
|
||||
|
@ -838,7 +823,7 @@ describe('content projection', () => {
|
|||
elementProperty(1, 'ngIf', bind(ctx.showing));
|
||||
}
|
||||
|
||||
function IfTemplate(rf1: RenderFlags, ctx1: any) {
|
||||
function IfTemplate(rf1: RenderFlags, ctx1: any, child: any) {
|
||||
if (rf1 & RenderFlags.Create) {
|
||||
projectionDef();
|
||||
projection(0);
|
||||
|
@ -884,27 +869,6 @@ describe('content projection', () => {
|
|||
});
|
||||
|
||||
it('should project into dynamic views (with insertion)', () => {
|
||||
class NgIf {
|
||||
constructor(public vcr: ViewContainerRef, public template: TemplateRef<any>) {}
|
||||
|
||||
@Input()
|
||||
set ngIf(value: boolean) {
|
||||
if (value) {
|
||||
const viewRef = this.template.createEmbeddedView({});
|
||||
this.vcr.insert(viewRef);
|
||||
} else {
|
||||
this.vcr.clear();
|
||||
}
|
||||
}
|
||||
|
||||
static ngDirectiveDef = defineDirective({
|
||||
type: NgIf,
|
||||
selectors: [['', 'ngIf', '']],
|
||||
inputs: {'ngIf': 'ngIf'},
|
||||
factory: () => new NgIf(injectViewContainerRef(), injectTemplateRef())
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* Before-
|
||||
* <ng-template [ngIf]="showing">
|
||||
|
@ -923,7 +887,7 @@ describe('content projection', () => {
|
|||
elementProperty(1, 'ngIf', bind(ctx.showing));
|
||||
}
|
||||
|
||||
function IfTemplate(rf1: RenderFlags, ctx1: any) {
|
||||
function IfTemplate(rf1: RenderFlags, ctx1: any, child: any) {
|
||||
if (rf1 & RenderFlags.Create) {
|
||||
projectionDef();
|
||||
projection(0);
|
||||
|
|
|
@ -8,11 +8,12 @@
|
|||
|
||||
import {Component, ComponentFactoryResolver, Directive, EmbeddedViewRef, NgModuleRef, Pipe, PipeTransform, RendererFactory2, TemplateRef, ViewContainerRef, createInjector, defineInjector, ɵAPP_ROOT as APP_ROOT, ɵNgModuleDef as NgModuleDef} from '../../src/core';
|
||||
import {getOrCreateNodeInjectorForNode, getOrCreateTemplateRef} from '../../src/render3/di';
|
||||
import {NgOnChangesFeature, defineComponent, defineDirective, definePipe, injectComponentFactoryResolver, injectTemplateRef, injectViewContainerRef} from '../../src/render3/index';
|
||||
import {bind, container, containerRefreshEnd, containerRefreshStart, element, elementEnd, elementProperty, elementStart, embeddedViewEnd, embeddedViewStart, interpolation1, load, loadDirective, projection, projectionDef, reserveSlots, text, textBinding} from '../../src/render3/instructions';
|
||||
import {AttributeMarker,NgOnChangesFeature, defineComponent, defineDirective, definePipe, injectComponentFactoryResolver, injectTemplateRef, injectViewContainerRef} from '../../src/render3/index';
|
||||
import {bind, container, containerRefreshEnd, containerRefreshStart, element, elementEnd, elementProperty, elementStart, embeddedViewEnd, embeddedViewStart, interpolation1, interpolation3, load, loadDirective, projection, projectionDef, reserveSlots, text, textBinding} from '../../src/render3/instructions';
|
||||
import {RenderFlags} from '../../src/render3/interfaces/definition';
|
||||
import {NgModuleFactory} from '../../src/render3/ng_module_ref';
|
||||
import {pipe, pipeBind1} from '../../src/render3/pipe';
|
||||
import {NgForOf} from '../../test/render3/common_with_def';
|
||||
|
||||
import {getRendererFactory2} from './imported_renderer2';
|
||||
import {ComponentFixture, TemplateFixture, createComponent} from './render_util';
|
||||
|
@ -447,6 +448,224 @@ describe('ViewContainerRef', () => {
|
|||
});
|
||||
});
|
||||
|
||||
describe('insertion points and declaration points', () => {
|
||||
class InsertionDir {
|
||||
// @Input()
|
||||
set tplDir(tpl: TemplateRef<any>|null) {
|
||||
tpl ? this.vcr.createEmbeddedView(tpl) : this.vcr.clear();
|
||||
}
|
||||
|
||||
constructor(public vcr: ViewContainerRef) {}
|
||||
|
||||
static ngDirectiveDef = defineDirective({
|
||||
type: InsertionDir,
|
||||
selectors: [['', 'tplDir', '']],
|
||||
factory: () => new InsertionDir(injectViewContainerRef()),
|
||||
inputs: {tplDir: 'tplDir'}
|
||||
});
|
||||
}
|
||||
|
||||
// see running stackblitz example: https://stackblitz.com/edit/angular-w3myy6
|
||||
it('should work with a template declared in a different component view from insertion',
|
||||
() => {
|
||||
let child: Child|null = null;
|
||||
|
||||
/**
|
||||
* <div [tplDir]="tpl">{{ name }}</div>
|
||||
* // template insertion point
|
||||
*/
|
||||
class Child {
|
||||
name = 'Child';
|
||||
tpl: TemplateRef<any>|null = null;
|
||||
|
||||
static ngComponentDef = defineComponent({
|
||||
type: Child,
|
||||
selectors: [['child']],
|
||||
factory: () => child = new Child(),
|
||||
template: function(rf: RenderFlags, ctx: any) {
|
||||
if (rf & RenderFlags.Create) {
|
||||
elementStart(0, 'div', [AttributeMarker.SelectOnly, 'tplDir']);
|
||||
{ text(1); }
|
||||
elementEnd();
|
||||
}
|
||||
if (rf & RenderFlags.Update) {
|
||||
elementProperty(0, 'tplDir', bind(ctx.tpl));
|
||||
textBinding(1, bind(ctx.name));
|
||||
}
|
||||
},
|
||||
inputs: {tpl: 'tpl'},
|
||||
directives: () => [InsertionDir]
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* // template declaration point
|
||||
* <ng-template #foo>
|
||||
* <div> {{ name }} </div>
|
||||
* </ng-template>
|
||||
*
|
||||
* <child [tpl]="foo"></child> <-- template insertion inside
|
||||
*/
|
||||
const Parent = createComponent('parent', function(rf: RenderFlags, ctx: any) {
|
||||
if (rf & RenderFlags.Create) {
|
||||
container(0, template);
|
||||
elementStart(1, 'child');
|
||||
elementEnd();
|
||||
}
|
||||
|
||||
if (rf & RenderFlags.Update) {
|
||||
// Hack until we have local refs for templates
|
||||
const tplRef = getOrCreateTemplateRef(getOrCreateNodeInjectorForNode(load(0)));
|
||||
elementProperty(1, 'tpl', bind(tplRef));
|
||||
}
|
||||
|
||||
function template(rf1: RenderFlags, ctx1: any, parent: any) {
|
||||
if (rf1 & RenderFlags.Create) {
|
||||
elementStart(0, 'div');
|
||||
{ text(1); }
|
||||
elementEnd();
|
||||
}
|
||||
|
||||
if (rf1 & RenderFlags.Update) {
|
||||
textBinding(1, bind(parent.name));
|
||||
}
|
||||
}
|
||||
}, [Child]);
|
||||
|
||||
const fixture = new ComponentFixture(Parent);
|
||||
fixture.component.name = 'Parent';
|
||||
fixture.update();
|
||||
|
||||
// Context should be inherited from the declaration point, not the insertion point,
|
||||
// so the template should read 'Parent'.
|
||||
expect(fixture.html).toEqual(`<child><div>Child</div><div>Parent</div></child>`);
|
||||
|
||||
child !.tpl = null;
|
||||
fixture.update();
|
||||
expect(fixture.html).toEqual(`<child><div>Child</div></child>`);
|
||||
});
|
||||
|
||||
// see running stackblitz example: https://stackblitz.com/edit/angular-3vplec
|
||||
it('should work with nested for loops with different declaration / insertion points', () => {
|
||||
/**
|
||||
* <ng-template ngFor [ngForOf]="rows" [ngForTemplate]="tpl">
|
||||
* // insertion point for templates (both row and cell)
|
||||
* </ng-template>
|
||||
*/
|
||||
class LoopComp {
|
||||
name = 'Loop';
|
||||
|
||||
// @Input()
|
||||
tpl !: TemplateRef<any>;
|
||||
|
||||
// @Input()
|
||||
rows !: any[];
|
||||
|
||||
static ngComponentDef = defineComponent({
|
||||
type: LoopComp,
|
||||
selectors: [['loop-comp']],
|
||||
factory: () => new LoopComp(),
|
||||
template: function(rf: RenderFlags, loop: any) {
|
||||
if (rf & RenderFlags.Create) {
|
||||
container(0, () => {}, null, [AttributeMarker.SelectOnly, 'ngForOf']);
|
||||
}
|
||||
|
||||
if (rf & RenderFlags.Update) {
|
||||
elementProperty(0, 'ngForOf', bind(loop.rows));
|
||||
elementProperty(0, 'ngForTemplate', bind(loop.tpl));
|
||||
}
|
||||
},
|
||||
inputs: {tpl: 'tpl', rows: 'rows'},
|
||||
directives: () => [NgForOf]
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* // row declaration point
|
||||
* <ng-template #rowTemplate let-row>
|
||||
*
|
||||
* // cell declaration point
|
||||
* <ng-template #cellTemplate let-cell>
|
||||
* <div> {{ cell }} - {{ row.value }} - {{ name }} </div>
|
||||
* </ng-template>
|
||||
*
|
||||
* <loop-comp [tpl]="cellTemplate" [rows]="row.data"></loop-comp> <-- cell insertion
|
||||
* </ng-template>
|
||||
*
|
||||
* <loop-comp [tpl]="rowTemplate" [rows]="rows"> <-- row insertion
|
||||
* </loop-comp>
|
||||
*/
|
||||
const Parent = createComponent('parent', function(rf: RenderFlags, parent: any) {
|
||||
if (rf & RenderFlags.Create) {
|
||||
container(0, rowTemplate);
|
||||
elementStart(1, 'loop-comp');
|
||||
elementEnd();
|
||||
}
|
||||
|
||||
if (rf & RenderFlags.Update) {
|
||||
// Hack until we have local refs for templates
|
||||
const rowTemplateRef = getOrCreateTemplateRef(getOrCreateNodeInjectorForNode(load(0)));
|
||||
elementProperty(1, 'tpl', bind(rowTemplateRef));
|
||||
elementProperty(1, 'rows', bind(parent.rows));
|
||||
}
|
||||
|
||||
function rowTemplate(rf1: RenderFlags, row: any, parent: any) {
|
||||
if (rf1 & RenderFlags.Create) {
|
||||
container(0, cellTemplate);
|
||||
elementStart(1, 'loop-comp');
|
||||
elementEnd();
|
||||
}
|
||||
|
||||
if (rf1 & RenderFlags.Update) {
|
||||
// Hack until we have local refs for templates
|
||||
const cellTemplateRef =
|
||||
getOrCreateTemplateRef(getOrCreateNodeInjectorForNode(load(0)));
|
||||
elementProperty(1, 'tpl', bind(cellTemplateRef));
|
||||
elementProperty(1, 'rows', bind(row.$implicit.data));
|
||||
}
|
||||
}
|
||||
|
||||
function cellTemplate(rf1: RenderFlags, cell: any, row: any, parent: any) {
|
||||
if (rf1 & RenderFlags.Create) {
|
||||
elementStart(0, 'div');
|
||||
{ text(1); }
|
||||
elementEnd();
|
||||
}
|
||||
|
||||
if (rf1 & RenderFlags.Update) {
|
||||
textBinding(
|
||||
1, interpolation3(
|
||||
'', cell.$implicit, ' - ', row.$implicit.value, ' - ', parent.name, ''));
|
||||
}
|
||||
}
|
||||
}, [LoopComp]);
|
||||
|
||||
const fixture = new ComponentFixture(Parent);
|
||||
fixture.component.name = 'Parent';
|
||||
fixture.component.rows =
|
||||
[{data: ['1', '2'], value: 'one'}, {data: ['3', '4'], value: 'two'}];
|
||||
fixture.update();
|
||||
|
||||
expect(fixture.html)
|
||||
.toEqual(
|
||||
'<loop-comp>' +
|
||||
'<loop-comp><div>1 - one - Parent</div><div>2 - one - Parent</div></loop-comp>' +
|
||||
'<loop-comp><div>3 - two - Parent</div><div>4 - two - Parent</div></loop-comp>' +
|
||||
'</loop-comp>');
|
||||
|
||||
fixture.component.rows = [{data: ['5', '6'], value: 'three'}, {data: ['7'], value: 'four'}];
|
||||
fixture.component.name = 'New name!';
|
||||
fixture.update();
|
||||
|
||||
expect(fixture.html)
|
||||
.toEqual(
|
||||
'<loop-comp>' +
|
||||
'<loop-comp><div>5 - three - New name!</div><div>6 - three - New name!</div></loop-comp>' +
|
||||
'<loop-comp><div>7 - four - New name!</div></loop-comp>' +
|
||||
'</loop-comp>');
|
||||
});
|
||||
});
|
||||
|
||||
const rendererFactory = getRendererFactory2(document);
|
||||
|
||||
describe('detach', () => {
|
||||
|
|
Loading…
Reference in New Issue