diff --git a/packages/upgrade/src/common/angular1.ts b/packages/upgrade/src/common/angular1.ts
index 3cbe8897d2..bd7d22e366 100644
--- a/packages/upgrade/src/common/angular1.ts
+++ b/packages/upgrade/src/common/angular1.ts
@@ -127,6 +127,7 @@ export type IAugmentedJQuery = Node[] & {
controller?: (name: string) => any;
isolateScope?: () => IScope;
injector?: () => IInjectorService;
+ triggerHandler?: (eventTypeOrObject: string | Event, extraParameters?: any[]) => IAugmentedJQuery;
remove?: () => void;
removeData?: () => void;
};
diff --git a/packages/upgrade/src/common/upgrade_helper.ts b/packages/upgrade/src/common/upgrade_helper.ts
index 8782de9c9e..999f83f1d0 100644
--- a/packages/upgrade/src/common/upgrade_helper.ts
+++ b/packages/upgrade/src/common/upgrade_helper.ts
@@ -124,6 +124,7 @@ export class UpgradeHelper {
controllerInstance.$onDestroy();
}
$scope.$destroy();
+ this.$element.triggerHandler !('$destroy');
}
prepareTransclusion(): angular.ILinkFn|undefined {
diff --git a/packages/upgrade/test/dynamic/upgrade_spec.ts b/packages/upgrade/test/dynamic/upgrade_spec.ts
index c99717d826..2e17c04410 100644
--- a/packages/upgrade/test/dynamic/upgrade_spec.ts
+++ b/packages/upgrade/test/dynamic/upgrade_spec.ts
@@ -2132,6 +2132,96 @@ withEachNg1Version(() => {
}));
});
+ describe('destroying the upgraded component', () => {
+ it('should destroy `componentScope`', fakeAsync(() => {
+ const adapter: UpgradeAdapter = new UpgradeAdapter(forwardRef(() => Ng2Module));
+ const scopeDestroyListener = jasmine.createSpy('scopeDestroyListener');
+ let ng2ComponentInstance: Ng2Component;
+
+ @Component({selector: 'ng2', template: '
'})
+ class Ng2Component {
+ ng2Destroy: boolean = false;
+ constructor() { ng2ComponentInstance = this; }
+ }
+
+ // On browsers that don't support `requestAnimationFrame` (IE 9, Android <= 4.3),
+ // `$animate` will use `setTimeout(..., 16.6)` instead. This timeout will still be on
+ // the queue at the end of the test, causing it to fail.
+ // Mocking animations (via `ngAnimateMock`) avoids the issue.
+ angular.module('ng1', ['ngAnimateMock'])
+ .component('ng1', {
+ controller: function($scope: angular.IScope) {
+ $scope.$on('$destroy', scopeDestroyListener);
+ },
+ })
+ .directive('ng2', adapter.downgradeNg2Component(Ng2Component));
+
+ @NgModule({
+ declarations: [adapter.upgradeNg1Component('ng1'), Ng2Component],
+ imports: [BrowserModule],
+ })
+ class Ng2Module {
+ }
+
+ const element = html('');
+ adapter.bootstrap(element, ['ng1']).ready((ref) => {
+ const $rootScope = ref.ng1RootScope as any;
+
+ expect(scopeDestroyListener).not.toHaveBeenCalled();
+
+ ng2ComponentInstance.ng2Destroy = true;
+ tick();
+ $rootScope.$digest();
+
+ expect(scopeDestroyListener).toHaveBeenCalledTimes(1);
+ });
+ }));
+
+ it('should emit `$destroy` on `$element`', fakeAsync(() => {
+ const adapter: UpgradeAdapter = new UpgradeAdapter(forwardRef(() => Ng2Module));
+ const elementDestroyListener = jasmine.createSpy('elementDestroyListener');
+ let ng2ComponentInstance: Ng2Component;
+
+ @Component({selector: 'ng2', template: '
'})
+ class Ng2Component {
+ ng2Destroy: boolean = false;
+ constructor() { ng2ComponentInstance = this; }
+ }
+
+ // On browsers that don't support `requestAnimationFrame` (IE 9, Android <= 4.3),
+ // `$animate` will use `setTimeout(..., 16.6)` instead. This timeout will still be on
+ // the queue at the end of the test, causing it to fail.
+ // Mocking animations (via `ngAnimateMock`) avoids the issue.
+ angular.module('ng1', ['ngAnimateMock'])
+ .component('ng1', {
+ controller: function($element: angular.IAugmentedJQuery) {
+ $element.on !('$destroy', elementDestroyListener);
+ },
+ })
+ .directive('ng2', adapter.downgradeNg2Component(Ng2Component));
+
+ @NgModule({
+ declarations: [adapter.upgradeNg1Component('ng1'), Ng2Component],
+ imports: [BrowserModule],
+ })
+ class Ng2Module {
+ }
+
+ const element = html('');
+ adapter.bootstrap(element, ['ng1']).ready((ref) => {
+ const $rootScope = ref.ng1RootScope as any;
+
+ expect(elementDestroyListener).not.toHaveBeenCalled();
+
+ ng2ComponentInstance.ng2Destroy = true;
+ tick();
+ $rootScope.$digest();
+
+ expect(elementDestroyListener).toHaveBeenCalledTimes(1);
+ });
+ }));
+ });
+
describe('linking', () => {
it('should run the pre-linking after instantiating the controller', async(() => {
const adapter: UpgradeAdapter = new UpgradeAdapter(forwardRef(() => Ng2Module));
diff --git a/packages/upgrade/test/static/integration/upgrade_component_spec.ts b/packages/upgrade/test/static/integration/upgrade_component_spec.ts
index 9c0ee94db2..7e4e623477 100644
--- a/packages/upgrade/test/static/integration/upgrade_component_spec.ts
+++ b/packages/upgrade/test/static/integration/upgrade_component_spec.ts
@@ -3629,7 +3629,68 @@ withEachNg1Version(() => {
ng2ComponentAInstance.destroyIt = true;
$digest(adapter);
- expect(scopeDestroyListener).toHaveBeenCalled();
+ expect(scopeDestroyListener).toHaveBeenCalledTimes(1);
+ });
+ }));
+
+ it('should emit `$destroy` on `$element`', async(() => {
+ const elementDestroyListener = jasmine.createSpy('elementDestroyListener');
+ let ng2ComponentAInstance: Ng2ComponentA;
+
+ // Define `ng1Component`
+ const ng1Component: angular.IComponent = {
+ controller: class {
+ constructor($element: angular.IAugmentedJQuery) {
+ $element.on !('$destroy', elementDestroyListener);
+ }
+ }
+ };
+
+ // Define `Ng1ComponentFacade`
+ @Directive({selector: 'ng1'})
+ class Ng1ComponentFacade extends UpgradeComponent {
+ constructor(elementRef: ElementRef, injector: Injector) {
+ super('ng1', elementRef, injector);
+ }
+ }
+
+ // Define `Ng2Component`
+ @Component({selector: 'ng2A', template: ''})
+ class Ng2ComponentA {
+ destroyIt = false;
+
+ constructor() { ng2ComponentAInstance = this; }
+ }
+
+ @Component({selector: 'ng2B', template: ''})
+ class Ng2ComponentB {
+ }
+
+ // Define `ng1Module`
+ const ng1Module = angular.module('ng1Module', [])
+ .component('ng1', ng1Component)
+ .directive('ng2A', downgradeComponent({component: Ng2ComponentA}));
+
+ // Define `Ng2Module`
+ @NgModule({
+ declarations: [Ng1ComponentFacade, Ng2ComponentA, Ng2ComponentB],
+ entryComponents: [Ng2ComponentA],
+ imports: [BrowserModule, UpgradeModule]
+ })
+ class Ng2Module {
+ ngDoBootstrap() {}
+ }
+
+ // Bootstrap
+ const element = html(``);
+
+ bootstrap(platformBrowserDynamic(), Ng2Module, element, ng1Module).then(adapter => {
+ expect(elementDestroyListener).not.toHaveBeenCalled();
+
+ ng2ComponentAInstance.destroyIt = true;
+ $digest(adapter);
+
+ expect(elementDestroyListener).toHaveBeenCalledTimes(1);
});
}));