fix(core): don’t detach nested view containers when destroying a view

When a view is destroyed, we destroy all
views in view containers and should not detach them. However, previously, we also detached them which lead to problems during the iteration loop.

Closes #8458
Closes #8471

Introduced by 0c600cf6e3
This commit is contained in:
Tobias Bosch 2016-05-04 11:53:12 -07:00
parent b30ddfbfc5
commit e2b1e1577d
3 changed files with 37 additions and 10 deletions

View File

@ -192,13 +192,7 @@ export abstract class AppView<T> {
ObservableWrapper.dispose(this.subscriptions[i]);
}
this.destroyInternal();
if (this._hasExternalHostElement) {
this.renderer.detachView(this.flatRootNodes);
} else if (isPresent(this.viewContainerElement)) {
this.viewContainerElement.detachView(this.viewContainerElement.nestedViews.indexOf(this));
} else {
this.dirtyParentQueriesInternal();
}
this.dirtyParentQueriesInternal();
this.renderer.destroyView(hostElement, this.allNodes);
}

View File

@ -1005,14 +1005,18 @@ export function main() {
}));
it('should be called after processing the content and view children', fakeAsync(() => {
var ctx = createCompWithContentAndViewChild();
var ctx = createCompFixture(
'<div testDirective="parent"><div *ngFor="let x of [0,1]" testDirective="contentChild{{x}}"></div>' +
'<other-cmp></other-cmp></div>',
TestComponent,
tcb.overrideTemplate(AnotherComponent, '<div testDirective="viewChild"></div>'));
ctx.detectChanges(false);
ctx.destroy();
expect(directiveLog.filter(['ngOnDestroy']))
.toEqual(
['contentChild.ngOnDestroy', 'viewChild.ngOnDestroy', 'parent.ngOnDestroy']);
['contentChild0.ngOnDestroy', 'contentChild1.ngOnDestroy', 'viewChild.ngOnDestroy', 'parent.ngOnDestroy']);
}));
it('should be called in reverse order so the child is always notified before the parent',

View File

@ -483,6 +483,35 @@ function declareTests(isJit: boolean) {
});
}));
it('should not detach views in ViewContainers when the parent view is destroyed.',
inject([TestComponentBuilder, AsyncTestCompleter], (tcb: TestComponentBuilder, async) => {
tcb.overrideView(
MyComp, new ViewMetadata({
template:
'<div *ngIf="ctxBoolProp"><template some-viewport let-greeting="someTmpl"><span>{{greeting}}</span></template></div>',
directives: [SomeViewport]
}))
.createAsync(MyComp)
.then((fixture) => {
fixture.debugElement.componentInstance.ctxBoolProp = true;
fixture.detectChanges();
var ngIfEl = fixture.debugElement.children[0];
var someViewport:SomeViewport = ngIfEl.childNodes[0].inject(SomeViewport);
expect(someViewport.container.length).toBe(2);
expect(ngIfEl.children.length).toBe(2);
fixture.debugElement.componentInstance.ctxBoolProp = false;
fixture.detectChanges();
expect(someViewport.container.length).toBe(2);
expect(fixture.debugElement.children.length).toBe(0);
async.done();
});
}));
it('should use a comment while stamping out `<template>` elements.',
inject([TestComponentBuilder, AsyncTestCompleter], (tcb: TestComponentBuilder, async) => {
tcb.overrideView(MyComp, new ViewMetadata({template: '<template></template>'}))
@ -2165,7 +2194,7 @@ class SomeViewportContext {
@Directive({selector: '[some-viewport]'})
@Injectable()
class SomeViewport {
constructor(container: ViewContainerRef, templateRef: TemplateRef<SomeViewportContext>) {
constructor(public container: ViewContainerRef, templateRef: TemplateRef<SomeViewportContext>) {
container.createEmbeddedView(templateRef, new SomeViewportContext('hello'));
container.createEmbeddedView(templateRef, new SomeViewportContext('again'));
}