fix(ivy): ViewRef.detachFromAppRef should clean the DOM (#29159)
PR Close #29159
This commit is contained in:
parent
29f57e315e
commit
eccbc785b3
|
@ -243,6 +243,15 @@ export function addRemoveViewFromContainer(
|
|||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Detach a `LView` from the DOM by detaching its nodes.
|
||||
*
|
||||
* @param lView the `LView` to be detached.
|
||||
*/
|
||||
export function renderDetachView(lView: LView) {
|
||||
walkTNodeTree(lView, WalkTNodeTreeAction.Detach, lView[RENDERER], null);
|
||||
}
|
||||
|
||||
/**
|
||||
* Traverses down and up the tree of views and containers to remove listeners and
|
||||
* call onDestroy callbacks.
|
||||
|
|
|
@ -14,7 +14,7 @@ import {EmbeddedViewRef as viewEngine_EmbeddedViewRef, InternalViewRef as viewEn
|
|||
import {checkNoChangesInRootView, checkNoChangesInternal, detectChangesInRootView, detectChangesInternal, markViewDirty, storeCleanupFn} from './instructions';
|
||||
import {TElementNode, TNode, TNodeType, TViewNode} from './interfaces/node';
|
||||
import {FLAGS, HOST, LView, LViewFlags, T_HOST} from './interfaces/view';
|
||||
import {destroyLView} from './node_manipulation';
|
||||
import {destroyLView, renderDetachView} from './node_manipulation';
|
||||
import {findComponentView, getLViewParent} from './util/view_traversal_utils';
|
||||
import {getNativeByTNode} from './util/view_utils';
|
||||
|
||||
|
@ -262,7 +262,10 @@ export class ViewRef<T> implements viewEngine_EmbeddedViewRef<T>, viewEngine_Int
|
|||
this._viewContainerRef = vcRef;
|
||||
}
|
||||
|
||||
detachFromAppRef() { this._appRef = null; }
|
||||
detachFromAppRef() {
|
||||
this._appRef = null;
|
||||
renderDetachView(this._lView);
|
||||
}
|
||||
|
||||
attachToAppRef(appRef: ApplicationRef) {
|
||||
if (this._viewContainerRef) {
|
||||
|
|
|
@ -0,0 +1,56 @@
|
|||
/**
|
||||
* @license
|
||||
* Copyright Google Inc. All Rights Reserved.
|
||||
*
|
||||
* Use of this source code is governed by an MIT-style license that can be
|
||||
* found in the LICENSE file at https://angular.io/license
|
||||
*/
|
||||
|
||||
import {ApplicationRef, Component, ComponentFactoryResolver, ComponentRef, ElementRef, Injector, NgModule} from '@angular/core';
|
||||
import {InternalViewRef} from '@angular/core/src/linker/view_ref';
|
||||
import {TestBed} from '@angular/core/testing';
|
||||
|
||||
|
||||
describe('ViewRef', () => {
|
||||
it('should remove nodes from DOM when the view is detached from app ref', () => {
|
||||
|
||||
@Component({selector: 'dynamic-cpt', template: '<div></div>'})
|
||||
class DynamicComponent {
|
||||
constructor(public elRef: ElementRef) {}
|
||||
}
|
||||
|
||||
@Component({template: `<span></span>`})
|
||||
class App {
|
||||
componentRef !: ComponentRef<DynamicComponent>;
|
||||
constructor(
|
||||
public appRef: ApplicationRef, private cfr: ComponentFactoryResolver,
|
||||
private injector: Injector) {}
|
||||
|
||||
create() {
|
||||
const componentFactory = this.cfr.resolveComponentFactory(DynamicComponent);
|
||||
this.componentRef = componentFactory.create(this.injector);
|
||||
(this.componentRef.hostView as InternalViewRef).attachToAppRef(this.appRef);
|
||||
document.body.appendChild(this.componentRef.instance.elRef.nativeElement);
|
||||
}
|
||||
|
||||
destroy() { (this.componentRef.hostView as InternalViewRef).detachFromAppRef(); }
|
||||
}
|
||||
|
||||
@NgModule({declarations: [App, DynamicComponent], entryComponents: [DynamicComponent]})
|
||||
class MyTestModule {
|
||||
}
|
||||
|
||||
TestBed.configureTestingModule({imports: [MyTestModule]});
|
||||
const fixture = TestBed.createComponent(App);
|
||||
fixture.detectChanges();
|
||||
|
||||
const appComponent = fixture.componentInstance;
|
||||
appComponent.create();
|
||||
fixture.detectChanges();
|
||||
expect(document.body.querySelector('dynamic-cpt')).not.toBeUndefined();
|
||||
|
||||
appComponent.destroy();
|
||||
fixture.detectChanges();
|
||||
expect(document.body.querySelector('dynamic-cpt')).toBeUndefined();
|
||||
});
|
||||
});
|
|
@ -1145,6 +1145,9 @@
|
|||
{
|
||||
"name": "renderComponentOrTemplate"
|
||||
},
|
||||
{
|
||||
"name": "renderDetachView"
|
||||
},
|
||||
{
|
||||
"name": "renderEmbeddedTemplate"
|
||||
},
|
||||
|
|
|
@ -17,18 +17,10 @@
|
|||
// tslint:disable
|
||||
|
||||
window.testBlocklist = {
|
||||
"Portals DomPortalOutlet should attach and detach a component portal without a ViewContainerRef": {
|
||||
"error": "Error: Expected '<pizza-msg><p>Pizza</p><p>Chocolate</p></pizza-msg>' to be '', 'Expected the DomPortalOutlet to be empty after detach'.",
|
||||
"notes": "Unknown"
|
||||
},
|
||||
"CdkTable should be able to render multiple header and footer rows": {
|
||||
"error": "Error: Missing definitions for header, footer, and row; cannot determine which columns should be rendered.",
|
||||
"notes": "Attempting to access content children before view is initialized"
|
||||
},
|
||||
"CdkTable should be able to render and change multiple header and footer rows": {
|
||||
"error": "Error: Missing definitions for header, footer, and row; cannot determine which columns should be rendered.",
|
||||
"notes": "Attempting to access content children before view is initialized"
|
||||
},
|
||||
"CdkTable should render correctly when using native HTML tags": {
|
||||
"error": "Error: Missing definitions for header, footer, and row; cannot determine which columns should be rendered.",
|
||||
"notes": "Unknown"
|
||||
|
|
Loading…
Reference in New Issue