From 21dcfc89e9e044adc3a11e52991e9579e3716345 Mon Sep 17 00:00:00 2001 From: vsavkin Date: Fri, 12 Jun 2015 14:14:29 -0700 Subject: [PATCH] fix(dynamic_component_loader): implemented dispose for dynamically-loaded components --- .../core/compiler/dynamic_component_loader.ts | 2 +- .../src/core/compiler/element_injector.ts | 20 +++++------ .../src/core/compiler/view_manager.ts | 8 +++++ .../compiler/dynamic_component_loader_spec.ts | 33 +++++++++++++++++-- 4 files changed, 49 insertions(+), 14 deletions(-) diff --git a/modules/angular2/src/core/compiler/dynamic_component_loader.ts b/modules/angular2/src/core/compiler/dynamic_component_loader.ts index 25c798059f..e71119a064 100644 --- a/modules/angular2/src/core/compiler/dynamic_component_loader.ts +++ b/modules/angular2/src/core/compiler/dynamic_component_loader.ts @@ -43,7 +43,7 @@ export class DynamicComponentLoader { this._viewManager.createDynamicComponentView(location, componentProtoViewRef, binding, injector); var component = this._viewManager.getComponent(location); - var dispose = () => { throw new BaseException("Not implemented"); }; + var dispose = () => { this._viewManager.destroyDynamicComponent(location); }; return new ComponentRef(location, component, dispose); }); } diff --git a/modules/angular2/src/core/compiler/element_injector.ts b/modules/angular2/src/core/compiler/element_injector.ts index bc735e9656..1d9169c816 100644 --- a/modules/angular2/src/core/compiler/element_injector.ts +++ b/modules/angular2/src/core/compiler/element_injector.ts @@ -688,22 +688,20 @@ export class ElementInjector extends TreeNode { this._preBuiltObjects = null; this._lightDomAppInjector = null; this._shadowDomAppInjector = null; - this._strategy.callOnDestroy(); - - if (isPresent(this._dynamicallyCreatedComponentBinding) && - this._dynamicallyCreatedComponentBinding.callOnDestroy) { - this._dynamicallyCreatedComponent.onDestroy(); - } - + this.destroyDynamicComponent(); this._strategy.clearInstances(); - - this._dynamicallyCreatedComponent = null; - this._dynamicallyCreatedComponentBinding = null; - this._constructionCounter = 0; } + destroyDynamicComponent(): void { + if (isPresent(this._dynamicallyCreatedComponentBinding) && + this._dynamicallyCreatedComponentBinding.callOnDestroy) { + this._dynamicallyCreatedComponent.onDestroy(); + this._dynamicallyCreatedComponentBinding = null; + this._dynamicallyCreatedComponent = null; + } + } hydrate(injector: Injector, host: ElementInjector, preBuiltObjects: PreBuiltObjects): void { var p = this._proto; diff --git a/modules/angular2/src/core/compiler/view_manager.ts b/modules/angular2/src/core/compiler/view_manager.ts index a1e73d0d18..d4c28894fc 100644 --- a/modules/angular2/src/core/compiler/view_manager.ts +++ b/modules/angular2/src/core/compiler/view_manager.ts @@ -133,6 +133,14 @@ export class AppViewManager { this._destroyFreeEmbeddedView(parentView, boundElementIndex, internalView(viewRef)); } + destroyDynamicComponent(location: ElementRef) { + var hostView = internalView(location.parentView); + var ei = hostView.elementInjectors[location.boundElementIndex]; + var componentView = hostView.componentChildViews[location.boundElementIndex]; + ei.destroyDynamicComponent(); + this._destroyComponentView(hostView, location.boundElementIndex, componentView); + } + createViewInContainer(viewContainerLocation: ElementRef, atIndex: number, protoViewRef: ProtoViewRef, context: ElementRef = null, injector: Injector = null): ViewRef { diff --git a/modules/angular2/test/core/compiler/dynamic_component_loader_spec.ts b/modules/angular2/test/core/compiler/dynamic_component_loader_spec.ts index 9c9305d81c..009ae3f944 100644 --- a/modules/angular2/test/core/compiler/dynamic_component_loader_spec.ts +++ b/modules/angular2/test/core/compiler/dynamic_component_loader_spec.ts @@ -17,7 +17,7 @@ import { import {TestBed, ViewProxy} from 'angular2/src/test_lib/test_bed'; import {Injector} from 'angular2/di'; -import {Component, View} from 'angular2/annotations'; +import {Component, View, onDestroy} from 'angular2/annotations'; import * as viewAnn from 'angular2/src/core/annotations_impl/view'; import {DynamicComponentLoader} from 'angular2/src/core/compiler/dynamic_component_loader'; import {ElementRef} from 'angular2/src/core/compiler/element_ref'; @@ -68,6 +68,28 @@ export function main() { }); })); + it('should allow destroying dynamically-loaded components', + inject([TestBed, AsyncTestCompleter], (tb, async) => { + tb.overrideView(MyComp, new viewAnn.View({ + template: '', + directives: [DynamicComp] + })); + + tb.createView(MyComp).then((view) => { + var dynamicComponent = view.rawView.locals.get("dynamic"); + dynamicComponent.done.then((ref) => { + view.detectChanges(); + expect(view.rootNodes).toHaveText("hello"); + + ref.dispose(); + + expect(ref.instance.destroyed).toBe(true); + expect(view.rootNodes).toHaveText(""); + async.done(); + }); + }); + })); + it('should allow to destroy and create them via viewcontainer directives', ijTestBed((tb: TestBed, async) => { tb.overrideView(MyComp, new viewAnn.View({ @@ -287,16 +309,23 @@ class DynamicComp { } } -@Component({selector: 'hello-cmp', appInjector: [DynamicallyCreatedComponentService]}) +@Component({ + selector: 'hello-cmp', + appInjector: [DynamicallyCreatedComponentService], + lifecycle: [onDestroy] +}) @View({template: "{{greeting}}"}) class DynamicallyCreatedCmp { greeting: string; dynamicallyCreatedComponentService: DynamicallyCreatedComponentService; + destroyed: boolean = false; constructor(a: DynamicallyCreatedComponentService) { this.greeting = "hello"; this.dynamicallyCreatedComponentService = a; } + + onDestroy() { this.destroyed = true; } } @Component({selector: 'dummy'})