fix(ivy): query nodes from different TemplateRefs inserted into one ViewContainerRef (#24254)
PR Close #24254
This commit is contained in:
parent
5cbcb5680b
commit
0561b66a2b
|
@ -706,7 +706,7 @@ export function getOrCreateTemplateRef<T>(di: LInjector): viewEngine_TemplateRef
|
||||||
ngDevMode && assertNotNull(hostTNode.tViews, 'TView must be allocated');
|
ngDevMode && assertNotNull(hostTNode.tViews, 'TView must be allocated');
|
||||||
di.templateRef = new TemplateRef<any>(
|
di.templateRef = new TemplateRef<any>(
|
||||||
getOrCreateElementRef(di), hostTNode.tViews as TView, hostNode.data.template !,
|
getOrCreateElementRef(di), hostTNode.tViews as TView, hostNode.data.template !,
|
||||||
getRenderer(), hostNode.queries);
|
getRenderer(), hostNode.data.queries);
|
||||||
}
|
}
|
||||||
return di.templateRef;
|
return di.templateRef;
|
||||||
}
|
}
|
||||||
|
|
|
@ -1514,16 +1514,20 @@ export function container(
|
||||||
// Containers are added to the current view tree instead of their embedded views
|
// Containers are added to the current view tree instead of their embedded views
|
||||||
// because views can be removed and re-inserted.
|
// because views can be removed and re-inserted.
|
||||||
addToViewTree(currentView, index, node.data);
|
addToViewTree(currentView, index, node.data);
|
||||||
|
|
||||||
|
const queries = node.queries;
|
||||||
|
if (queries) {
|
||||||
|
// prepare place for matching nodes from views inserted into a given container
|
||||||
|
lContainer.queries = queries.container();
|
||||||
|
}
|
||||||
|
|
||||||
createDirectivesAndLocals(localRefs);
|
createDirectivesAndLocals(localRefs);
|
||||||
|
|
||||||
isParent = false;
|
isParent = false;
|
||||||
ngDevMode && assertNodeType(previousOrParentNode, TNodeType.Container);
|
ngDevMode && assertNodeType(previousOrParentNode, TNodeType.Container);
|
||||||
const queries = node.queries;
|
|
||||||
if (queries) {
|
if (queries) {
|
||||||
// check if a given container node matches
|
// check if a given container node matches
|
||||||
queries.addNode(node);
|
queries.addNode(node);
|
||||||
// prepare place for matching nodes from views inserted into a given container
|
|
||||||
lContainer.queries = queries.container();
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -7,9 +7,10 @@
|
||||||
*/
|
*/
|
||||||
|
|
||||||
import {NgForOfContext} from '@angular/common';
|
import {NgForOfContext} from '@angular/common';
|
||||||
|
import {TemplateRef, ViewContainerRef} from '@angular/core';
|
||||||
|
|
||||||
import {QUERY_READ_CONTAINER_REF, QUERY_READ_ELEMENT_REF, QUERY_READ_FROM_NODE, QUERY_READ_TEMPLATE_REF} from '../../src/render3/di';
|
import {QUERY_READ_CONTAINER_REF, QUERY_READ_ELEMENT_REF, QUERY_READ_FROM_NODE, QUERY_READ_TEMPLATE_REF, getOrCreateNodeInjectorForNode, getOrCreateTemplateRef} from '../../src/render3/di';
|
||||||
import {QueryList, defineComponent, detectChanges} from '../../src/render3/index';
|
import {AttributeMarker, QueryList, defineComponent, defineDirective, detectChanges, injectViewContainerRef} from '../../src/render3/index';
|
||||||
import {bind, container, containerRefreshEnd, containerRefreshStart, elementEnd, elementProperty, elementStart, embeddedViewEnd, embeddedViewStart, load, loadDirective} from '../../src/render3/instructions';
|
import {bind, container, containerRefreshEnd, containerRefreshStart, elementEnd, elementProperty, elementStart, embeddedViewEnd, embeddedViewStart, load, loadDirective} from '../../src/render3/instructions';
|
||||||
import {RenderFlags} from '../../src/render3/interfaces/definition';
|
import {RenderFlags} from '../../src/render3/interfaces/definition';
|
||||||
import {query, queryRefresh} from '../../src/render3/query';
|
import {query, queryRefresh} from '../../src/render3/query';
|
||||||
|
@ -18,6 +19,7 @@ import {NgForOf, NgIf} from './common_with_def';
|
||||||
import {ComponentFixture, createComponent, createDirective, renderComponent} from './render_util';
|
import {ComponentFixture, createComponent, createDirective, renderComponent} from './render_util';
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Helper function to check if a given candidate object resembles ElementRef
|
* Helper function to check if a given candidate object resembles ElementRef
|
||||||
* @param candidate
|
* @param candidate
|
||||||
|
@ -690,6 +692,29 @@ describe('query', () => {
|
||||||
|
|
||||||
describe('ViewContainerRef', () => {
|
describe('ViewContainerRef', () => {
|
||||||
|
|
||||||
|
let directiveInstance: ViewContainerManipulatorDirective|null = null;
|
||||||
|
|
||||||
|
class ViewContainerManipulatorDirective {
|
||||||
|
static ngDirectiveDef = defineDirective({
|
||||||
|
type: ViewContainerManipulatorDirective,
|
||||||
|
selectors: [['', 'vc', '']],
|
||||||
|
factory: () => {
|
||||||
|
return directiveInstance =
|
||||||
|
new ViewContainerManipulatorDirective(injectViewContainerRef());
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
constructor(private _vcRef: ViewContainerRef) {}
|
||||||
|
|
||||||
|
insertTpl(tpl: TemplateRef<{}>, ctx: {}, idx?: number) {
|
||||||
|
this._vcRef.createEmbeddedView(tpl, ctx, idx);
|
||||||
|
}
|
||||||
|
|
||||||
|
remove(index?: number) { this._vcRef.remove(index); }
|
||||||
|
}
|
||||||
|
|
||||||
|
beforeEach(() => { directiveInstance = null; });
|
||||||
|
|
||||||
it('should report results in views inserted / removed by ngIf', () => {
|
it('should report results in views inserted / removed by ngIf', () => {
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
@ -782,6 +807,97 @@ describe('query', () => {
|
||||||
|
|
||||||
});
|
});
|
||||||
|
|
||||||
|
// https://stackblitz.com/edit/angular-rrmmuf?file=src/app/app.component.ts
|
||||||
|
it('should report results when different instances of TemplateRef are inserted into one ViewContainerRefs',
|
||||||
|
() => {
|
||||||
|
let tpl1: TemplateRef<{}>;
|
||||||
|
let tpl2: TemplateRef<{}>;
|
||||||
|
|
||||||
|
|
||||||
|
/**
|
||||||
|
* <ng-template #tpl1 let-idx="idx">
|
||||||
|
* <div #foo [id]="'foo1_'+idx"></div>
|
||||||
|
* </ng-template>
|
||||||
|
*
|
||||||
|
* <div #foo id="middle"></div>
|
||||||
|
*
|
||||||
|
* <ng-template #tpl2 let-idx="idx">
|
||||||
|
* <div #foo [id]="'foo2_'+idx"></div>
|
||||||
|
* </ng-template>
|
||||||
|
*
|
||||||
|
* <ng-template viewInserter #vi="vi"></ng-template>
|
||||||
|
*/
|
||||||
|
const Cmpt = createComponent('cmpt', function(rf: RenderFlags, ctx: any) {
|
||||||
|
let tmp: any;
|
||||||
|
if (rf & RenderFlags.Create) {
|
||||||
|
query(0, ['foo'], true, QUERY_READ_FROM_NODE);
|
||||||
|
|
||||||
|
container(1, (rf: RenderFlags, ctx: {idx: number}) => {
|
||||||
|
if (rf & RenderFlags.Create) {
|
||||||
|
elementStart(0, 'div', null, ['foo', '']);
|
||||||
|
elementEnd();
|
||||||
|
}
|
||||||
|
if (rf & RenderFlags.Update) {
|
||||||
|
elementProperty(0, 'id', bind('foo1_' + ctx.idx));
|
||||||
|
}
|
||||||
|
}, null, []);
|
||||||
|
|
||||||
|
elementStart(2, 'div', ['id', 'middle'], ['foo', '']);
|
||||||
|
elementEnd();
|
||||||
|
|
||||||
|
container(4, (rf: RenderFlags, ctx: {idx: number}) => {
|
||||||
|
if (rf & RenderFlags.Create) {
|
||||||
|
elementStart(0, 'div', null, ['foo', '']);
|
||||||
|
elementEnd();
|
||||||
|
}
|
||||||
|
if (rf & RenderFlags.Update) {
|
||||||
|
elementProperty(0, 'id', bind('foo2_' + ctx.idx));
|
||||||
|
}
|
||||||
|
}, null, []);
|
||||||
|
|
||||||
|
container(5, undefined, null, [AttributeMarker.SELECT_ONLY, 'vc']);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (rf & RenderFlags.Update) {
|
||||||
|
tpl1 = getOrCreateTemplateRef(getOrCreateNodeInjectorForNode(load(1)));
|
||||||
|
tpl2 = getOrCreateTemplateRef(getOrCreateNodeInjectorForNode(load(4)));
|
||||||
|
queryRefresh(tmp = load<QueryList<any>>(0)) && (ctx.query = tmp as QueryList<any>);
|
||||||
|
}
|
||||||
|
|
||||||
|
}, [ViewContainerManipulatorDirective]);
|
||||||
|
|
||||||
|
const fixture = new ComponentFixture(Cmpt);
|
||||||
|
const qList = fixture.component.query;
|
||||||
|
|
||||||
|
expect(qList.length).toBe(1);
|
||||||
|
expect(qList.first.nativeElement.getAttribute('id')).toBe('middle');
|
||||||
|
|
||||||
|
directiveInstance !.insertTpl(tpl1 !, {idx: 0}, 0);
|
||||||
|
directiveInstance !.insertTpl(tpl2 !, {idx: 1}, 1);
|
||||||
|
fixture.update();
|
||||||
|
expect(qList.length).toBe(3);
|
||||||
|
let qListArr = qList.toArray();
|
||||||
|
expect(qListArr[0].nativeElement.getAttribute('id')).toBe('foo1_0');
|
||||||
|
expect(qListArr[1].nativeElement.getAttribute('id')).toBe('middle');
|
||||||
|
expect(qListArr[2].nativeElement.getAttribute('id')).toBe('foo2_1');
|
||||||
|
|
||||||
|
directiveInstance !.insertTpl(tpl1 !, {idx: 1}, 1);
|
||||||
|
fixture.update();
|
||||||
|
expect(qList.length).toBe(4);
|
||||||
|
qListArr = qList.toArray();
|
||||||
|
expect(qListArr[0].nativeElement.getAttribute('id')).toBe('foo1_0');
|
||||||
|
expect(qListArr[1].nativeElement.getAttribute('id')).toBe('foo1_1');
|
||||||
|
expect(qListArr[2].nativeElement.getAttribute('id')).toBe('middle');
|
||||||
|
expect(qListArr[3].nativeElement.getAttribute('id')).toBe('foo2_1');
|
||||||
|
|
||||||
|
directiveInstance !.remove(1);
|
||||||
|
fixture.update();
|
||||||
|
expect(qList.length).toBe(3);
|
||||||
|
qListArr = qList.toArray();
|
||||||
|
expect(qListArr[0].nativeElement.getAttribute('id')).toBe('foo1_0');
|
||||||
|
expect(qListArr[1].nativeElement.getAttribute('id')).toBe('middle');
|
||||||
|
expect(qListArr[2].nativeElement.getAttribute('id')).toBe('foo2_1');
|
||||||
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
describe('JS blocks', () => {
|
describe('JS blocks', () => {
|
||||||
|
|
Loading…
Reference in New Issue