fix(upgrade): correctly destroy nested downgraded component (#22400)

Previously, when a downgraded component was destroyed in a way that did
not trigger the `$destroy` event on the element (e.g. when a parent
element was removed from the DOM by Angular, not AngularJS), the
`ComponentRef` was not destroyed and unregistered.
This commit fixes it by listening for the `$destroy` event on both the
element and the scope.

Fixes #22392

PR Close #22400
This commit is contained in:
George Kalpakas
2018-02-23 15:18:21 +02:00
committed by Alex Eagle
parent 83b32a0a0a
commit 8a85888773
6 changed files with 121 additions and 10 deletions

View File

@ -12,6 +12,11 @@ import {$ROOT_SCOPE} from '@angular/upgrade/src/common/constants';
export * from '../common/test_helpers';
export function $apply(adapter: UpgradeAdapterRef, exp: angular.Ng1Expression) {
const $rootScope = adapter.ng1Injector.get($ROOT_SCOPE) as angular.IRootScopeService;
$rootScope.$apply(exp);
}
export function $digest(adapter: UpgradeAdapterRef) {
const $rootScope = adapter.ng1Injector.get($ROOT_SCOPE) as angular.IRootScopeService;
$rootScope.$digest();

View File

@ -6,13 +6,13 @@
* found in the LICENSE file at https://angular.io/license
*/
import {ChangeDetectorRef, Component, EventEmitter, Input, NO_ERRORS_SCHEMA, NgModule, NgModuleFactory, NgZone, OnChanges, SimpleChange, SimpleChanges, Testability, destroyPlatform, forwardRef} from '@angular/core';
import {ChangeDetectorRef, Component, EventEmitter, Input, NO_ERRORS_SCHEMA, NgModule, NgModuleFactory, NgZone, OnChanges, OnDestroy, SimpleChange, SimpleChanges, Testability, destroyPlatform, forwardRef} from '@angular/core';
import {async, fakeAsync, flushMicrotasks, tick} from '@angular/core/testing';
import {BrowserModule} from '@angular/platform-browser';
import {platformBrowserDynamic} from '@angular/platform-browser-dynamic';
import * as angular from '@angular/upgrade/src/common/angular1';
import {UpgradeAdapter, UpgradeAdapterRef} from '@angular/upgrade/src/dynamic/upgrade_adapter';
import {$digest, html, multiTrim, withEachNg1Version} from './test_helpers';
import {$apply, $digest, html, multiTrim, withEachNg1Version} from './test_helpers';
declare global {
export var inject: Function;
@ -582,6 +582,49 @@ withEachNg1Version(() => {
});
}));
it('should properly run cleanup with multiple levels of nesting', async(() => {
const adapter: UpgradeAdapter = new UpgradeAdapter(forwardRef(() => Ng2Module));
let destroyed = false;
@Component(
{selector: 'ng2-outer', template: '<div *ngIf="!destroyIt"><ng1></ng1></div>'})
class Ng2OuterComponent {
@Input() destroyIt = false;
}
@Component({selector: 'ng2-inner', template: 'test'})
class Ng2InnerComponent implements OnDestroy {
ngOnDestroy() { destroyed = true; }
}
@NgModule({
imports: [BrowserModule],
declarations:
[Ng2InnerComponent, Ng2OuterComponent, adapter.upgradeNg1Component('ng1')],
schemas: [NO_ERRORS_SCHEMA],
})
class Ng2Module {
}
const ng1Module =
angular.module('ng1', [])
.directive('ng1', () => ({template: '<ng2-inner></ng2-inner>'}))
.directive('ng2Inner', adapter.downgradeNg2Component(Ng2InnerComponent))
.directive('ng2Outer', adapter.downgradeNg2Component(Ng2OuterComponent));
const element = html('<ng2-outer [destroy-it]="destroyIt"></ng2-outer>');
adapter.bootstrap(element, [ng1Module.name]).ready(ref => {
expect(element.textContent).toBe('test');
expect(destroyed).toBe(false);
$apply(ref, 'destroyIt = true');
expect(element.textContent).toBe('');
expect(destroyed).toBe(true);
});
}));
it('should fallback to the root ng2.injector when compiled outside the dom', async(() => {
const adapter: UpgradeAdapter = new UpgradeAdapter(forwardRef(() => Ng2Module));
const ng1Module = angular.module('ng1', []);