Skip to content

Commit 71b9d55

Browse files
pkozlowski-opensourcematsko
authored andcommitted
fix(ivy): remove query results from destroyed embedded views (angular#28445)
PR Close angular#28445
1 parent 7a22019 commit 71b9d55

File tree

3 files changed

+31
-19
lines changed

3 files changed

+31
-19
lines changed

packages/core/src/render3/node_manipulation.ts

Lines changed: 18 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -11,7 +11,7 @@ import {attachPatchData} from './context_discovery';
1111
import {callHooks} from './hooks';
1212
import {LContainer, NATIVE, VIEWS, unusedValueExportToPlacateAjd as unused1} from './interfaces/container';
1313
import {ComponentDef} from './interfaces/definition';
14-
import {TContainerNode, TElementContainerNode, TElementNode, TNode, TNodeFlags, TNodeType, TProjectionNode, TViewNode, unusedValueExportToPlacateAjd as unused2} from './interfaces/node';
14+
import {TContainerNode, TElementContainerNode, TElementNode, TNode, TNodeFlags, TNodeType, TViewNode, unusedValueExportToPlacateAjd as unused2} from './interfaces/node';
1515
import {unusedValueExportToPlacateAjd as unused3} from './interfaces/projection';
1616
import {ProceduralRenderer3, RComment, RElement, RNode, RText, Renderer3, isProceduralRenderer, unusedValueExportToPlacateAjd as unused4} from './interfaces/renderer';
1717
import {CLEANUP, CONTAINER_INDEX, FLAGS, HEADER_OFFSET, HOST_NODE, HookData, LView, LViewFlags, NEXT, PARENT, QUERIES, RENDERER, TVIEW, unusedValueExportToPlacateAjd as unused5} from './interfaces/view';
@@ -242,14 +242,17 @@ export function destroyViewTree(rootView: LView): void {
242242
while (viewOrContainer) {
243243
let next: LView|LContainer|null = null;
244244

245-
if (viewOrContainer.length >= HEADER_OFFSET) {
245+
if (isLContainer(viewOrContainer)) {
246+
// If container, traverse down to its first LView.
247+
const container = viewOrContainer as LContainer;
248+
const viewsInContainer = container[VIEWS];
249+
if (viewsInContainer.length) {
250+
next = viewsInContainer[0];
251+
}
252+
} else {
246253
// If LView, traverse down to child.
247254
const view = viewOrContainer as LView;
248255
if (view[TVIEW].childIndex > -1) next = getLViewChild(view);
249-
} else {
250-
// If container, traverse down to its first LView.
251-
const container = viewOrContainer as LContainer;
252-
if (container[VIEWS].length) next = container[VIEWS][0];
253256
}
254257

255258
if (next == null) {
@@ -258,6 +261,15 @@ export function destroyViewTree(rootView: LView): void {
258261
while (viewOrContainer && !viewOrContainer ![NEXT] && viewOrContainer !== rootView) {
259262
cleanUpView(viewOrContainer);
260263
viewOrContainer = getParentState(viewOrContainer, rootView);
264+
if (isLContainer(viewOrContainer)) {
265+
// this view will be destroyed so we need to notify queries that a view is detached
266+
const viewsInContainer = (viewOrContainer as LContainer)[VIEWS];
267+
for (let viewToDetach of viewsInContainer) {
268+
if (viewToDetach[QUERIES]) {
269+
viewToDetach[QUERIES] !.removeView();
270+
}
271+
}
272+
}
261273
}
262274
cleanUpView(viewOrContainer || rootView);
263275
next = viewOrContainer && viewOrContainer ![NEXT];

packages/core/src/render3/util.ts

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -127,7 +127,8 @@ export function isComponentDef<T>(def: DirectiveDef<T>): def is ComponentDef<T>
127127
return (def as ComponentDef<T>).template !== null;
128128
}
129129

130-
export function isLContainer(value: RElement | RComment | LContainer | StylingContext): boolean {
130+
export function isLContainer(
131+
value: RElement | RComment | LContainer | LView | StylingContext | null): boolean {
131132
// Styling contexts are also arrays, but their first index contains an element node
132133
return Array.isArray(value) && value.length === LCONTAINER_LENGTH;
133134
}

packages/core/test/linker/query_integration_spec.ts

Lines changed: 11 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -633,25 +633,24 @@ describe('Query API', () => {
633633
expect(q.query.map((d: TextDirective) => d.text)).toEqual(['2', '1']);
634634
});
635635

636-
fixmeIvy('FW-920: Queries in nested views are not destroyed properly')
637-
.it('should remove manually projected templates if their parent view is destroyed', () => {
638-
const template = `
636+
it('should remove manually projected templates if their parent view is destroyed', () => {
637+
const template = `
639638
<manual-projecting #q><ng-template #tpl><div text="1"></div></ng-template></manual-projecting>
640639
<div *ngIf="shouldShow">
641640
<ng-container [ngTemplateOutlet]="tpl"></ng-container>
642641
</div>
643642
`;
644-
const view = createTestCmp(MyComp0, template);
645-
const q = view.debugElement.children[0].references !['q'];
646-
view.componentInstance.shouldShow = true;
647-
view.detectChanges();
643+
const view = createTestCmp(MyComp0, template);
644+
const q = view.debugElement.children[0].references !['q'];
645+
view.componentInstance.shouldShow = true;
646+
view.detectChanges();
648647

649-
expect(q.query.length).toBe(1);
648+
expect(q.query.length).toBe(1);
650649

651-
view.componentInstance.shouldShow = false;
652-
view.detectChanges();
653-
expect(q.query.length).toBe(0);
654-
});
650+
view.componentInstance.shouldShow = false;
651+
view.detectChanges();
652+
expect(q.query.length).toBe(0);
653+
});
655654

656655
modifiedInIvy('https://github.com/angular/angular/issues/15117 fixed in ivy')
657656
.it('should not throw if a content template is queried and created in the view during change detection',

0 commit comments

Comments
 (0)