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:
parent
5e8bf2f88d
commit
014949f74c
|
@ -49,7 +49,7 @@ export interface LQueries {
|
||||||
* Notify `LQueries` that an `LView` has been removed from `LContainer`. As a result all
|
* 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.
|
* the matching nodes from this view should be removed from container's queries.
|
||||||
*/
|
*/
|
||||||
removeView(removeIndex: number): void;
|
removeView(): void;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Add additional `QueryList` to track.
|
* Add additional `QueryList` to track.
|
||||||
|
|
|
@ -393,7 +393,7 @@ export function detachView(container: LContainerNode, removeIndex: number): LVie
|
||||||
// Notify query that view has been removed
|
// Notify query that view has been removed
|
||||||
const removedLview = viewNode.data;
|
const removedLview = viewNode.data;
|
||||||
if (removedLview[QUERIES]) {
|
if (removedLview[QUERIES]) {
|
||||||
removedLview[QUERIES] !.removeView(removeIndex);
|
removedLview[QUERIES] !.removeView();
|
||||||
}
|
}
|
||||||
// Unsets the attached flag
|
// Unsets the attached flag
|
||||||
viewNode.data[FLAGS] &= ~LViewFlags.Attached;
|
viewNode.data[FLAGS] &= ~LViewFlags.Attached;
|
||||||
|
|
|
@ -176,13 +176,16 @@ export class LQueries_ implements LQueries {
|
||||||
add(this.deep, node);
|
add(this.deep, node);
|
||||||
}
|
}
|
||||||
|
|
||||||
removeView(index: number): void {
|
removeView(): void {
|
||||||
let query = this.deep;
|
let query = this.deep;
|
||||||
while (query) {
|
while (query) {
|
||||||
ngDevMode &&
|
ngDevMode &&
|
||||||
assertDefined(
|
assertDefined(
|
||||||
query.containerValues, 'View queries need to have a pointer to container values.');
|
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
|
// mark a query as dirty only when removed view had matching modes
|
||||||
ngDevMode && assertEqual(removed.length, 1, 'removed.length');
|
ngDevMode && assertEqual(removed.length, 1, 'removed.length');
|
||||||
|
|
|
@ -693,15 +693,17 @@ describe('query', () => {
|
||||||
|
|
||||||
describe('ViewContainerRef', () => {
|
describe('ViewContainerRef', () => {
|
||||||
|
|
||||||
let directiveInstance: ViewContainerManipulatorDirective|null = null;
|
let directiveInstances: ViewContainerManipulatorDirective[] = [];
|
||||||
|
|
||||||
class ViewContainerManipulatorDirective {
|
class ViewContainerManipulatorDirective {
|
||||||
static ngDirectiveDef = defineDirective({
|
static ngDirectiveDef = defineDirective({
|
||||||
type: ViewContainerManipulatorDirective,
|
type: ViewContainerManipulatorDirective,
|
||||||
selectors: [['', 'vc', '']],
|
selectors: [['', 'vc', '']],
|
||||||
factory: () => {
|
factory: () => {
|
||||||
return directiveInstance =
|
const directiveInstance =
|
||||||
new ViewContainerManipulatorDirective(injectViewContainerRef());
|
new ViewContainerManipulatorDirective(injectViewContainerRef());
|
||||||
|
directiveInstances.push(directiveInstance);
|
||||||
|
return directiveInstance;
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
|
@ -714,7 +716,7 @@ describe('query', () => {
|
||||||
remove(index?: number) { this._vcRef.remove(index); }
|
remove(index?: number) { this._vcRef.remove(index); }
|
||||||
}
|
}
|
||||||
|
|
||||||
beforeEach(() => { directiveInstance = null; });
|
beforeEach(() => { directiveInstances = []; });
|
||||||
|
|
||||||
it('should report results in views inserted / removed by ngIf', () => {
|
it('should report results in views inserted / removed by ngIf', () => {
|
||||||
|
|
||||||
|
@ -873,8 +875,8 @@ describe('query', () => {
|
||||||
expect(qList.length).toBe(1);
|
expect(qList.length).toBe(1);
|
||||||
expect(qList.first.nativeElement.getAttribute('id')).toBe('middle');
|
expect(qList.first.nativeElement.getAttribute('id')).toBe('middle');
|
||||||
|
|
||||||
directiveInstance !.insertTpl(tpl1 !, {idx: 0}, 0);
|
directiveInstances[0].insertTpl(tpl1 !, {idx: 0}, 0);
|
||||||
directiveInstance !.insertTpl(tpl2 !, {idx: 1}, 1);
|
directiveInstances[0].insertTpl(tpl2 !, {idx: 1}, 1);
|
||||||
fixture.update();
|
fixture.update();
|
||||||
expect(qList.length).toBe(3);
|
expect(qList.length).toBe(3);
|
||||||
let qListArr = qList.toArray();
|
let qListArr = qList.toArray();
|
||||||
|
@ -882,7 +884,7 @@ describe('query', () => {
|
||||||
expect(qListArr[1].nativeElement.getAttribute('id')).toBe('middle');
|
expect(qListArr[1].nativeElement.getAttribute('id')).toBe('middle');
|
||||||
expect(qListArr[2].nativeElement.getAttribute('id')).toBe('foo2_1');
|
expect(qListArr[2].nativeElement.getAttribute('id')).toBe('foo2_1');
|
||||||
|
|
||||||
directiveInstance !.insertTpl(tpl1 !, {idx: 1}, 1);
|
directiveInstances[0].insertTpl(tpl1 !, {idx: 1}, 1);
|
||||||
fixture.update();
|
fixture.update();
|
||||||
expect(qList.length).toBe(4);
|
expect(qList.length).toBe(4);
|
||||||
qListArr = qList.toArray();
|
qListArr = qList.toArray();
|
||||||
|
@ -891,13 +893,83 @@ describe('query', () => {
|
||||||
expect(qListArr[2].nativeElement.getAttribute('id')).toBe('middle');
|
expect(qListArr[2].nativeElement.getAttribute('id')).toBe('middle');
|
||||||
expect(qListArr[3].nativeElement.getAttribute('id')).toBe('foo2_1');
|
expect(qListArr[3].nativeElement.getAttribute('id')).toBe('foo2_1');
|
||||||
|
|
||||||
directiveInstance !.remove(1);
|
directiveInstances[0].remove(1);
|
||||||
fixture.update();
|
fixture.update();
|
||||||
expect(qList.length).toBe(3);
|
expect(qList.length).toBe(3);
|
||||||
qListArr = qList.toArray();
|
qListArr = qList.toArray();
|
||||||
expect(qListArr[0].nativeElement.getAttribute('id')).toBe('foo1_0');
|
expect(qListArr[0].nativeElement.getAttribute('id')).toBe('foo1_0');
|
||||||
expect(qListArr[1].nativeElement.getAttribute('id')).toBe('middle');
|
expect(qListArr[1].nativeElement.getAttribute('id')).toBe('middle');
|
||||||
expect(qListArr[2].nativeElement.getAttribute('id')).toBe('foo2_1');
|
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);
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
|
|
Loading…
Reference in New Issue