diff --git a/packages/upgrade/src/common/angular1.ts b/packages/upgrade/src/common/angular1.ts
index bd7d22e366897..3e9f7a135fb78 100644
--- a/packages/upgrade/src/common/angular1.ts
+++ b/packages/upgrade/src/common/angular1.ts
@@ -287,4 +287,7 @@ export const resumeBootstrap: typeof angular.resumeBootstrap = () => angular.res
export const getTestability: typeof angular.getTestability = e => angular.getTestability(e);
+export const cleanData: (l: NodeList | Node[]) => void = (nodes) =>
+ (angular.element as any).cleanData(nodes);
+
export let version = angular.version;
diff --git a/packages/upgrade/src/common/upgrade_helper.ts b/packages/upgrade/src/common/upgrade_helper.ts
index 999f83f1d025c..f1147530b9e5b 100644
--- a/packages/upgrade/src/common/upgrade_helper.ts
+++ b/packages/upgrade/src/common/upgrade_helper.ts
@@ -124,7 +124,8 @@ export class UpgradeHelper {
controllerInstance.$onDestroy();
}
$scope.$destroy();
- this.$element.triggerHandler !('$destroy');
+ angular.cleanData([this.element]);
+ angular.cleanData(this.element.querySelectorAll('*'));
}
prepareTransclusion(): angular.ILinkFn|undefined {
diff --git a/packages/upgrade/test/static/integration/upgrade_component_spec.ts b/packages/upgrade/test/static/integration/upgrade_component_spec.ts
index 7e4e62347761b..8124bbbf29dee 100644
--- a/packages/upgrade/test/static/integration/upgrade_component_spec.ts
+++ b/packages/upgrade/test/static/integration/upgrade_component_spec.ts
@@ -3694,6 +3694,206 @@ withEachNg1Version(() => {
});
}));
+ it('should emit `$destroy` on `$element` descendants', async(() => {
+ const elementDestroyListener = jasmine.createSpy('elementDestroyListener');
+ let ng2ComponentAInstance: Ng2ComponentA;
+
+ // Define `ng1Component`
+ const ng1Component: angular.IComponent = {
+ controller: class {
+ constructor($element: angular.IAugmentedJQuery) {
+ $element.contents !().on !('$destroy', elementDestroyListener);
+ }
+ },
+ template: '
'
+ };
+
+ // 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);
+ });
+ }));
+
+ it('should clear data on element and descendants`', async(() => {
+ let ng1ComponentElement: angular.IAugmentedJQuery;
+ let ng2ComponentAInstance: Ng2ComponentA;
+
+ // Define `ng1Component`
+ const ng1Component: angular.IComponent = {
+ controller: class {
+ constructor($element: angular.IAugmentedJQuery) {
+ $element.data !('test', 1);
+ $element.contents !().data !('test', 2);
+
+ ng1ComponentElement = $element;
+ }
+ },
+ template: ''
+ };
+
+ // 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(ng1ComponentElement.data !('test')).toBe(1);
+ expect(ng1ComponentElement.contents !().data !('test')).toBe(2);
+
+ ng2ComponentAInstance.destroyIt = true;
+ $digest(adapter);
+
+ expect(ng1ComponentElement.data !('test')).toBeUndefined();
+ expect(ng1ComponentElement.contents !().data !('test')).toBeUndefined();
+ });
+ }));
+
+ it('should clear dom listeners on element and descendants`', async(() => {
+ const elementClickListener = jasmine.createSpy('elementClickListener');
+ const descendantClickListener = jasmine.createSpy('descendantClickListener');
+ let ng1DescendantElement: angular.IAugmentedJQuery;
+ let ng2ComponentAInstance: Ng2ComponentA;
+
+ // Define `ng1Component`
+ const ng1Component: angular.IComponent = {
+ controller: class {
+ constructor($element: angular.IAugmentedJQuery) {
+ ng1DescendantElement = $element.contents !();
+
+ $element.on !('click', elementClickListener);
+ ng1DescendantElement.on !('click', descendantClickListener);
+ }
+ },
+ template: ''
+ };
+
+ // 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 => {
+ (ng1DescendantElement[0] as HTMLElement).click();
+ expect(elementClickListener).toHaveBeenCalledTimes(1);
+ expect(descendantClickListener).toHaveBeenCalledTimes(1);
+
+ ng2ComponentAInstance.destroyIt = true;
+ $digest(adapter);
+
+ (ng1DescendantElement[0] as HTMLElement).click();
+ expect(elementClickListener).toHaveBeenCalledTimes(1);
+ expect(descendantClickListener).toHaveBeenCalledTimes(1);
+ });
+ }));
+
it('should clean up `$doCheck()` watchers from the parent scope', async(() => {
let ng2Component: Ng2Component;