fix(ivy): correctly handle queries with embedded views (#24418)

This PR takes care of all the remaining cases where embedded view definition
and insertion points are different.

PR Close #24418
This commit is contained in:
Pawel Kozlowski 2018-06-11 11:51:16 +02:00 committed by Miško Hevery
parent 5e8bf2f88d
commit 014949f74c
4 changed files with 87 additions and 12 deletions

View File

@ -49,7 +49,7 @@ export interface LQueries {
* Notify `LQueries` that an `LView` has been removed from `LContainer`. As a result all
* the matching nodes from this view should be removed from container's queries.
*/
removeView(removeIndex: number): void;
removeView(): void;
/**
* Add additional `QueryList` to track.

View File

@ -393,7 +393,7 @@ export function detachView(container: LContainerNode, removeIndex: number): LVie
// Notify query that view has been removed
const removedLview = viewNode.data;
if (removedLview[QUERIES]) {
removedLview[QUERIES] !.removeView(removeIndex);
removedLview[QUERIES] !.removeView();
}
// Unsets the attached flag
viewNode.data[FLAGS] &= ~LViewFlags.Attached;

View File

@ -176,13 +176,16 @@ export class LQueries_ implements LQueries {
add(this.deep, node);
}
removeView(index: number): void {
removeView(): void {
let query = this.deep;
while (query) {
ngDevMode &&
assertDefined(
query.containerValues, 'View queries need to have a pointer to container values.');
const removed = query.containerValues !.splice(index, 1);
const containerValues = query.containerValues !;
const viewValuesIdx = containerValues.indexOf(query.values);
const removed = containerValues.splice(viewValuesIdx, 1);
// mark a query as dirty only when removed view had matching modes
ngDevMode && assertEqual(removed.length, 1, 'removed.length');

View File

@ -693,15 +693,17 @@ describe('query', () => {
describe('ViewContainerRef', () => {
let directiveInstance: ViewContainerManipulatorDirective|null = null;
let directiveInstances: ViewContainerManipulatorDirective[] = [];
class ViewContainerManipulatorDirective {
static ngDirectiveDef = defineDirective({
type: ViewContainerManipulatorDirective,
selectors: [['', 'vc', '']],
factory: () => {
return directiveInstance =
new ViewContainerManipulatorDirective(injectViewContainerRef());
const directiveInstance =
new ViewContainerManipulatorDirective(injectViewContainerRef());
directiveInstances.push(directiveInstance);
return directiveInstance;
}
});
@ -714,7 +716,7 @@ describe('query', () => {
remove(index?: number) { this._vcRef.remove(index); }
}
beforeEach(() => { directiveInstance = null; });
beforeEach(() => { directiveInstances = []; });
it('should report results in views inserted / removed by ngIf', () => {
@ -873,8 +875,8 @@ describe('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);
directiveInstances[0].insertTpl(tpl1 !, {idx: 0}, 0);
directiveInstances[0].insertTpl(tpl2 !, {idx: 1}, 1);
fixture.update();
expect(qList.length).toBe(3);
let qListArr = qList.toArray();
@ -882,7 +884,7 @@ describe('query', () => {
expect(qListArr[1].nativeElement.getAttribute('id')).toBe('middle');
expect(qListArr[2].nativeElement.getAttribute('id')).toBe('foo2_1');
directiveInstance !.insertTpl(tpl1 !, {idx: 1}, 1);
directiveInstances[0].insertTpl(tpl1 !, {idx: 1}, 1);
fixture.update();
expect(qList.length).toBe(4);
qListArr = qList.toArray();
@ -891,13 +893,83 @@ describe('query', () => {
expect(qListArr[2].nativeElement.getAttribute('id')).toBe('middle');
expect(qListArr[3].nativeElement.getAttribute('id')).toBe('foo2_1');
directiveInstance !.remove(1);
directiveInstances[0].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');
directiveInstances[0].remove(1);
fixture.update();
expect(qList.length).toBe(2);
qListArr = qList.toArray();
expect(qListArr[0].nativeElement.getAttribute('id')).toBe('foo1_0');
expect(qListArr[1].nativeElement.getAttribute('id')).toBe('middle');
});
// https://stackblitz.com/edit/angular-7vvo9j?file=src%2Fapp%2Fapp.component.ts
it('should report results when the same TemplateRef is inserted into different ViewContainerRefs',
() => {
let tpl: TemplateRef<{}>;
/**
* <ng-template #tpl let-idx="idx" let-container_idx="container_idx">
* <div #foo [id]="'foo_'+container_idx+'_'+idx"></div>
* </ng-template>
*
* <ng-template viewInserter #vi1="vi"></ng-template>
* <ng-template viewInserter #vi2="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, container_idx: number}) => {
if (rf & RenderFlags.Create) {
elementStart(0, 'div', null, ['foo', '']);
elementEnd();
}
if (rf & RenderFlags.Update) {
elementProperty(0, 'id', bind('foo_' + ctx.container_idx + '_' + ctx.idx));
}
}, null, []);
container(2, undefined, null, [AttributeMarker.SELECT_ONLY, 'vc']);
container(3, undefined, null, [AttributeMarker.SELECT_ONLY, 'vc']);
}
if (rf & RenderFlags.Update) {
tpl = getOrCreateTemplateRef(getOrCreateNodeInjectorForNode(load(1)));
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(0);
directiveInstances[0].insertTpl(tpl !, {idx: 0, container_idx: 0}, 0);
directiveInstances[1].insertTpl(tpl !, {idx: 0, container_idx: 1}, 0);
fixture.update();
expect(qList.length).toBe(2);
let qListArr = qList.toArray();
expect(qListArr[0].nativeElement.getAttribute('id')).toBe('foo_1_0');
expect(qListArr[1].nativeElement.getAttribute('id')).toBe('foo_0_0');
directiveInstances[0].remove();
fixture.update();
expect(qList.length).toBe(1);
qListArr = qList.toArray();
expect(qListArr[0].nativeElement.getAttribute('id')).toBe('foo_1_0');
directiveInstances[1].remove();
fixture.update();
expect(qList.length).toBe(0);
});
});