From 1ff1792642ded576a5d2d896ee909715bfdd7059 Mon Sep 17 00:00:00 2001 From: Alex Rickabaugh Date: Tue, 10 Nov 2015 10:40:33 -0800 Subject: [PATCH] fix(core): Unload components when individually disposed. --- modules/angular2/src/core/application_ref.ts | 39 +++++++++++++++---- .../angular2/test/core/application_spec.ts | 19 ++++++++- 2 files changed, 49 insertions(+), 9 deletions(-) diff --git a/modules/angular2/src/core/application_ref.ts b/modules/angular2/src/core/application_ref.ts index 8d54589bbc..aa7514b9a1 100644 --- a/modules/angular2/src/core/application_ref.ts +++ b/modules/angular2/src/core/application_ref.ts @@ -69,10 +69,15 @@ function _componentProviders(appComponentType: Type): Array { + useFactory: (dynamicComponentLoader: DynamicComponentLoader, appRef: ApplicationRef_, + injector: Injector) => { + // Save the ComponentRef for disposal later. + var ref: ComponentRef; // TODO(rado): investigate whether to support providers on root component. - return dynamicComponentLoader.loadAsRoot(appComponentType, null, injector) + return dynamicComponentLoader.loadAsRoot(appComponentType, null, injector, + () => { appRef._unloadComponent(ref); }) .then((componentRef) => { + ref = componentRef; if (isPresent(componentRef.location.nativeElement)) { injector.get(TestabilityRegistry) .registerApplication(componentRef.location.nativeElement, @@ -81,7 +86,7 @@ function _componentProviders(appComponentType: Type): Array): Promise { var completer = PromiseWrapper.completer(); @@ -408,12 +417,8 @@ export class ApplicationRef_ extends ApplicationRef { var injector: Injector = this._injector.resolveAndCreateChild(componentProviders); var compRefToken: Promise = injector.get(APP_COMPONENT_REF_PROMISE); var tick = (componentRef) => { - var appChangeDetector = internalView(componentRef.hostView).changeDetector; - this._changeDetectorRefs.push(appChangeDetector.ref); - this.tick(); + this._loadComponent(componentRef); completer.resolve(componentRef); - this._rootComponents.push(componentRef); - this._bootstrapListeners.forEach((listener) => listener(componentRef)); }; var tickResult = PromiseWrapper.then(compRefToken, tick); @@ -429,6 +434,24 @@ export class ApplicationRef_ extends ApplicationRef { return completer.promise; } + /** @internal */ + _loadComponent(ref): void { + var appChangeDetector = internalView(ref.hostView).changeDetector; + this._changeDetectorRefs.push(appChangeDetector.ref); + this.tick(); + this._rootComponents.push(ref); + this._bootstrapListeners.forEach((listener) => listener(ref)); + } + + /** @internal */ + _unloadComponent(ref): void { + if (!ListWrapper.contains(this._rootComponents, ref)) { + return; + } + this.unregisterChangeDetector(internalView(ref.hostView).changeDetector.ref); + ListWrapper.remove(this._rootComponents, ref); + } + get injector(): Injector { return this._injector; } get zone(): NgZone { return this._zone; } diff --git a/modules/angular2/test/core/application_spec.ts b/modules/angular2/test/core/application_spec.ts index af3ecd1a4c..1ed6f3a5db 100644 --- a/modules/angular2/test/core/application_spec.ts +++ b/modules/angular2/test/core/application_spec.ts @@ -12,7 +12,8 @@ import { } from 'angular2/testing_internal'; import {IS_DART, isPresent, stringify} from 'angular2/src/facade/lang'; import {bootstrap} from 'angular2/bootstrap'; -import {ApplicationRef} from 'angular2/src/core/application_ref'; +import {platform, applicationDomProviders} from 'angular2/src/core/application_common'; +import {applicationCommonProviders, ApplicationRef} from 'angular2/src/core/application_ref'; import {Component, Directive, View} from 'angular2/core'; import {DOM} from 'angular2/src/core/dom/dom_adapter'; import {DOCUMENT} from 'angular2/render'; @@ -21,6 +22,7 @@ import {provide, Inject, Injector} from 'angular2/core'; import {ExceptionHandler} from 'angular2/src/facade/exceptions'; import {Testability, TestabilityRegistry} from 'angular2/src/core/testability/testability'; import {ComponentRef_} from "angular2/src/core/linker/dynamic_component_loader"; +import {compilerProviders} from 'angular2/src/compiler/compiler'; @Component({selector: 'hello-app'}) @View({template: '{{greeting}} world!'}) @@ -161,6 +163,21 @@ export function main() { async.done(); }); })); + it('should unregister change detectors when components are disposed', + inject([AsyncTestCompleter], (async) => { + var app = platform().application([ + applicationCommonProviders(), + applicationDomProviders(), + compilerProviders(), + testProviders + ]); + app.bootstrap(HelloRootCmp) + .then((ref) => { + ref.dispose(); + expect(() => app.tick()).not.toThrow(); + async.done(); + }); + })); it("should make the provided bindings available to the application component", inject([AsyncTestCompleter], (async) => {