fix(ivy): query nodes from different TemplateRefs inserted into one ViewContainerRef (#24254)

PR Close #24254
This commit is contained in:
Pawel Kozlowski 2018-06-01 17:01:24 +02:00 committed by Victor Berchet
parent 5cbcb5680b
commit 0561b66a2b
3 changed files with 126 additions and 6 deletions

View File

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

View File

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

View File

@ -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', () => {