Skip to content

Commit eccbc78

Browse files
marclavalkara
authored andcommitted
fix(ivy): ViewRef.detachFromAppRef should clean the DOM (angular#29159)
PR Close angular#29159
1 parent 29f57e3 commit eccbc78

File tree

5 files changed

+73
-10
lines changed

5 files changed

+73
-10
lines changed

packages/core/src/render3/node_manipulation.ts

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -243,6 +243,15 @@ export function addRemoveViewFromContainer(
243243
}
244244
}
245245

246+
/**
247+
* Detach a `LView` from the DOM by detaching its nodes.
248+
*
249+
* @param lView the `LView` to be detached.
250+
*/
251+
export function renderDetachView(lView: LView) {
252+
walkTNodeTree(lView, WalkTNodeTreeAction.Detach, lView[RENDERER], null);
253+
}
254+
246255
/**
247256
* Traverses down and up the tree of views and containers to remove listeners and
248257
* call onDestroy callbacks.

packages/core/src/render3/view_ref.ts

Lines changed: 5 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -14,7 +14,7 @@ import {EmbeddedViewRef as viewEngine_EmbeddedViewRef, InternalViewRef as viewEn
1414
import {checkNoChangesInRootView, checkNoChangesInternal, detectChangesInRootView, detectChangesInternal, markViewDirty, storeCleanupFn} from './instructions';
1515
import {TElementNode, TNode, TNodeType, TViewNode} from './interfaces/node';
1616
import {FLAGS, HOST, LView, LViewFlags, T_HOST} from './interfaces/view';
17-
import {destroyLView} from './node_manipulation';
17+
import {destroyLView, renderDetachView} from './node_manipulation';
1818
import {findComponentView, getLViewParent} from './util/view_traversal_utils';
1919
import {getNativeByTNode} from './util/view_utils';
2020

@@ -262,7 +262,10 @@ export class ViewRef<T> implements viewEngine_EmbeddedViewRef<T>, viewEngine_Int
262262
this._viewContainerRef = vcRef;
263263
}
264264

265-
detachFromAppRef() { this._appRef = null; }
265+
detachFromAppRef() {
266+
this._appRef = null;
267+
renderDetachView(this._lView);
268+
}
266269

267270
attachToAppRef(appRef: ApplicationRef) {
268271
if (this._viewContainerRef) {
Lines changed: 56 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,56 @@
1+
/**
2+
* @license
3+
* Copyright Google Inc. All Rights Reserved.
4+
*
5+
* Use of this source code is governed by an MIT-style license that can be
6+
* found in the LICENSE file at https://angular.io/license
7+
*/
8+
9+
import {ApplicationRef, Component, ComponentFactoryResolver, ComponentRef, ElementRef, Injector, NgModule} from '@angular/core';
10+
import {InternalViewRef} from '@angular/core/src/linker/view_ref';
11+
import {TestBed} from '@angular/core/testing';
12+
13+
14+
describe('ViewRef', () => {
15+
it('should remove nodes from DOM when the view is detached from app ref', () => {
16+
17+
@Component({selector: 'dynamic-cpt', template: '<div></div>'})
18+
class DynamicComponent {
19+
constructor(public elRef: ElementRef) {}
20+
}
21+
22+
@Component({template: `<span></span>`})
23+
class App {
24+
componentRef !: ComponentRef<DynamicComponent>;
25+
constructor(
26+
public appRef: ApplicationRef, private cfr: ComponentFactoryResolver,
27+
private injector: Injector) {}
28+
29+
create() {
30+
const componentFactory = this.cfr.resolveComponentFactory(DynamicComponent);
31+
this.componentRef = componentFactory.create(this.injector);
32+
(this.componentRef.hostView as InternalViewRef).attachToAppRef(this.appRef);
33+
document.body.appendChild(this.componentRef.instance.elRef.nativeElement);
34+
}
35+
36+
destroy() { (this.componentRef.hostView as InternalViewRef).detachFromAppRef(); }
37+
}
38+
39+
@NgModule({declarations: [App, DynamicComponent], entryComponents: [DynamicComponent]})
40+
class MyTestModule {
41+
}
42+
43+
TestBed.configureTestingModule({imports: [MyTestModule]});
44+
const fixture = TestBed.createComponent(App);
45+
fixture.detectChanges();
46+
47+
const appComponent = fixture.componentInstance;
48+
appComponent.create();
49+
fixture.detectChanges();
50+
expect(document.body.querySelector('dynamic-cpt')).not.toBeUndefined();
51+
52+
appComponent.destroy();
53+
fixture.detectChanges();
54+
expect(document.body.querySelector('dynamic-cpt')).toBeUndefined();
55+
});
56+
});

packages/core/test/bundling/todo/bundle.golden_symbols.json

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1145,6 +1145,9 @@
11451145
{
11461146
"name": "renderComponentOrTemplate"
11471147
},
1148+
{
1149+
"name": "renderDetachView"
1150+
},
11481151
{
11491152
"name": "renderEmbeddedTemplate"
11501153
},

tools/material-ci/angular_material_test_blocklist.js

Lines changed: 0 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -17,18 +17,10 @@
1717
// tslint:disable
1818

1919
window.testBlocklist = {
20-
"Portals DomPortalOutlet should attach and detach a component portal without a ViewContainerRef": {
21-
"error": "Error: Expected '<pizza-msg><p>Pizza</p><p>Chocolate</p></pizza-msg>' to be '', 'Expected the DomPortalOutlet to be empty after detach'.",
22-
"notes": "Unknown"
23-
},
2420
"CdkTable should be able to render multiple header and footer rows": {
2521
"error": "Error: Missing definitions for header, footer, and row; cannot determine which columns should be rendered.",
2622
"notes": "Attempting to access content children before view is initialized"
2723
},
28-
"CdkTable should be able to render and change multiple header and footer rows": {
29-
"error": "Error: Missing definitions for header, footer, and row; cannot determine which columns should be rendered.",
30-
"notes": "Attempting to access content children before view is initialized"
31-
},
3224
"CdkTable should render correctly when using native HTML tags": {
3325
"error": "Error: Missing definitions for header, footer, and row; cannot determine which columns should be rendered.",
3426
"notes": "Unknown"

0 commit comments

Comments
 (0)