fix(ivy): remove DOM nodes from their real parent vs saved parent (#28455)

Currently, DOM node removal called `removeChild` on the saved parent
node when destroying a component. However, this will fail if the
component has been manually moved in the DOM. This change makes the
removal always use the node's real `parentNode` and ignore the provided
`parent`.

PR Close #28455
This commit is contained in:
Jeremy Elbourn
2019-02-01 15:38:42 -08:00
committed by Matias Niemelä
parent 5a2c3ff8b5
commit 89eac702b5
5 changed files with 84 additions and 21 deletions

View File

@ -6,7 +6,7 @@
* found in the LICENSE file at https://angular.io/license
*/
import {Attribute, ChangeDetectionStrategy, ChangeDetectorRef, Component, ComponentFactoryResolver, DebugElement, Directive, ElementRef, Host, Inject, InjectionToken, Injector, Input, NgModule, Optional, Pipe, PipeTransform, Provider, Self, SkipSelf, TemplateRef, Type, ViewContainerRef} from '@angular/core';
import {Attribute, ChangeDetectionStrategy, ChangeDetectorRef, Component, ComponentFactoryResolver, DebugElement, Directive, ElementRef, EmbeddedViewRef, Host, Inject, InjectionToken, Injector, Input, NgModule, Optional, Pipe, PipeTransform, Provider, Self, SkipSelf, TemplateRef, Type, ViewContainerRef} from '@angular/core';
import {ComponentFixture, TestBed, fakeAsync} from '@angular/core/testing';
import {getDOM} from '@angular/platform-browser/src/dom/dom_adapter';
import {expect} from '@angular/platform-browser/testing/src/matchers';
@ -1021,6 +1021,58 @@ class TestComp {
expect(impurePipe4).not.toBe(impurePipe1);
});
});
describe('view destruction', () => {
@Component({selector: 'some-component', template: ''})
class SomeComponent {
}
@NgModule({
declarations: [SomeComponent],
exports: [SomeComponent],
entryComponents: [SomeComponent]
})
class SomeModule {
}
@Component({selector: 'listener-and-on-destroy', template: ''})
class ComponentThatLoadsAnotherComponentThenMovesIt {
constructor(
private viewContainerRef: ViewContainerRef,
private componentFactoryResolver: ComponentFactoryResolver) {}
ngOnInit() {
// Dynamically load some component.
const componentFactory =
this.componentFactoryResolver.resolveComponentFactory(SomeComponent);
const componentRef =
this.viewContainerRef.createComponent(componentFactory, this.viewContainerRef.length);
// Manually move the loaded component to some arbitrary DOM node.
const componentRootNode =
(componentRef.hostView as EmbeddedViewRef<any>).rootNodes[0] as HTMLElement;
document.createElement('div').appendChild(componentRootNode);
// Destroy the component we just moved to ensure that it does not error during
// destruction.
componentRef.destroy();
}
}
it('should not error when destroying a component that has been moved in the DOM', () => {
TestBed.configureTestingModule({
imports: [SomeModule],
declarations: [ComponentThatLoadsAnotherComponentThenMovesIt],
});
const fixture =
createComponentFixture(`<listener-and-on-destroy></listener-and-on-destroy>`);
fixture.detectChanges();
// This test will fail if the ngOnInit of ComponentThatLoadsAnotherComponentThenMovesIt
// throws an error.
});
});
});
})();