diff --git a/packages/upgrade/src/common/downgrade_component.ts b/packages/upgrade/src/common/downgrade_component.ts index 14ebc80738..a6d736582c 100644 --- a/packages/upgrade/src/common/downgrade_component.ts +++ b/packages/upgrade/src/common/downgrade_component.ts @@ -115,7 +115,7 @@ export function downgradeComponent(info: { facade.createComponent(projectableNodes); facade.setupInputs(needsNgZone, info.propagateDigest); facade.setupOutputs(); - facade.registerCleanup(needsNgZone); + facade.registerCleanup(); injectorPromise.resolve(facade.getInjector()); diff --git a/packages/upgrade/src/common/downgrade_component_adapter.ts b/packages/upgrade/src/common/downgrade_component_adapter.ts index 9f66465133..314cd8cf3c 100644 --- a/packages/upgrade/src/common/downgrade_component_adapter.ts +++ b/packages/upgrade/src/common/downgrade_component_adapter.ts @@ -25,7 +25,6 @@ export class DowngradeComponentAdapter { private componentRef: ComponentRef; private component: any; private changeDetector: ChangeDetectorRef; - private appRef: ApplicationRef; constructor( private element: angular.IAugmentedJQuery, private attrs: angular.IAttributes, @@ -35,7 +34,6 @@ export class DowngradeComponentAdapter { private componentFactory: ComponentFactory, private wrapCallback: (cb: () => T) => () => T) { this.componentScope = scope.$new(); - this.appRef = parentInjector.get(ApplicationRef); } compileContents(): Node[][] { @@ -154,7 +152,8 @@ export class DowngradeComponentAdapter { // Attach the view so that it will be dirty-checked. if (needsNgZone) { - this.appRef.attachView(this.componentRef.hostView); + const appRef = this.parentInjector.get(ApplicationRef); + appRef.attachView(this.componentRef.hostView); } } @@ -202,7 +201,7 @@ export class DowngradeComponentAdapter { } } - registerCleanup(needsNgZone: boolean) { + registerCleanup() { const destroyComponentRef = this.wrapCallback(() => this.componentRef.destroy()); this.element.on !('$destroy', () => { @@ -210,9 +209,6 @@ export class DowngradeComponentAdapter { this.componentRef.injector.get(TestabilityRegistry) .unregisterApplication(this.componentRef.location.nativeElement); destroyComponentRef(); - if (needsNgZone) { - this.appRef.detachView(this.componentRef.hostView); - } }); } diff --git a/packages/upgrade/test/static/integration/downgrade_module_spec.ts b/packages/upgrade/test/static/integration/downgrade_module_spec.ts index d3617d7b63..71012a6942 100644 --- a/packages/upgrade/test/static/integration/downgrade_module_spec.ts +++ b/packages/upgrade/test/static/integration/downgrade_module_spec.ts @@ -6,7 +6,7 @@ * found in the LICENSE file at https://angular.io/license */ -import {Component, Inject, Injector, Input, NgModule, NgZone, OnChanges, OnDestroy, StaticProvider, destroyPlatform} from '@angular/core'; +import {ApplicationRef, Component, Inject, Injector, Input, NgModule, NgZone, OnChanges, OnDestroy, StaticProvider, ViewRef, destroyPlatform} from '@angular/core'; import {async, fakeAsync, tick} from '@angular/core/testing'; import {BrowserModule} from '@angular/platform-browser'; import {platformBrowserDynamic} from '@angular/platform-browser-dynamic'; @@ -306,6 +306,53 @@ export function main() { }); })); + it('should detach hostViews from the ApplicationRef once destroyed', async(() => { + let ng2Component: Ng2Component; + + @Component({selector: 'ng2', template: ''}) + class Ng2Component { + constructor(public appRef: ApplicationRef) { + ng2Component = this; + spyOn(appRef, 'attachView').and.callThrough(); + spyOn(appRef, 'detachView').and.callThrough(); + } + } + + @NgModule({ + declarations: [Ng2Component], + entryComponents: [Ng2Component], + imports: [BrowserModule], + }) + class Ng2Module { + ngDoBootstrap() {} + } + + const bootstrapFn = (extraProviders: StaticProvider[]) => + platformBrowserDynamic(extraProviders).bootstrapModule(Ng2Module); + const lazyModuleName = downgradeModule(bootstrapFn); + const ng1Module = + angular.module('ng1', [lazyModuleName]) + .directive( + 'ng2', downgradeComponent({component: Ng2Component, propagateDigest})); + + const element = html(''); + const $injector = angular.bootstrap(element, [ng1Module.name]); + const $rootScope = $injector.get($ROOT_SCOPE) as angular.IRootScopeService; + + // Wait for the module to be bootstrapped. + setTimeout(() => { + const hostView: ViewRef = + (ng2Component.appRef.attachView as jasmine.Spy).calls.mostRecent().args[0]; + + expect(hostView.destroyed).toBe(false); + + $rootScope.$apply('hideNg2 = true'); + + expect(hostView.destroyed).toBe(true); + expect(ng2Component.appRef.detachView).toHaveBeenCalledWith(hostView); + }); + })); + it('should only retrieve the Angular zone once (and cache it for later use)', fakeAsync(() => { let count = 0;