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]); ObservableWrapper.dispose(this.subscriptions[i]);
} }
this.destroyInternal(); this.destroyInternal();
if (this._hasExternalHostElement) { this.dirtyParentQueriesInternal();
this.renderer.detachView(this.flatRootNodes);
} else if (isPresent(this.viewContainerElement)) {
this.viewContainerElement.detachView(this.viewContainerElement.nestedViews.indexOf(this));
} else {
this.dirtyParentQueriesInternal();
}
this.renderer.destroyView(hostElement, this.allNodes); 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(() => { 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.detectChanges(false);
ctx.destroy(); ctx.destroy();
expect(directiveLog.filter(['ngOnDestroy'])) expect(directiveLog.filter(['ngOnDestroy']))
.toEqual( .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', 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.', it('should use a comment while stamping out `<template>` elements.',
inject([TestComponentBuilder, AsyncTestCompleter], (tcb: TestComponentBuilder, async) => { inject([TestComponentBuilder, AsyncTestCompleter], (tcb: TestComponentBuilder, async) => {
tcb.overrideView(MyComp, new ViewMetadata({template: '<template></template>'})) tcb.overrideView(MyComp, new ViewMetadata({template: '<template></template>'}))
@ -2165,7 +2194,7 @@ class SomeViewportContext {
@Directive({selector: '[some-viewport]'}) @Directive({selector: '[some-viewport]'})
@Injectable() @Injectable()
class SomeViewport { 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('hello'));
container.createEmbeddedView(templateRef, new SomeViewportContext('again')); container.createEmbeddedView(templateRef, new SomeViewportContext('again'));
} }