fix(upgrade): trigger $destroy
event on upgraded component element (#25357)
Fixes #25334 PR Close #25357
This commit is contained in:
parent
71007ef9b2
commit
2a672a97ab
@ -127,6 +127,7 @@ export type IAugmentedJQuery = Node[] & {
|
|||||||
controller?: (name: string) => any;
|
controller?: (name: string) => any;
|
||||||
isolateScope?: () => IScope;
|
isolateScope?: () => IScope;
|
||||||
injector?: () => IInjectorService;
|
injector?: () => IInjectorService;
|
||||||
|
triggerHandler?: (eventTypeOrObject: string | Event, extraParameters?: any[]) => IAugmentedJQuery;
|
||||||
remove?: () => void;
|
remove?: () => void;
|
||||||
removeData?: () => void;
|
removeData?: () => void;
|
||||||
};
|
};
|
||||||
|
@ -124,6 +124,7 @@ export class UpgradeHelper {
|
|||||||
controllerInstance.$onDestroy();
|
controllerInstance.$onDestroy();
|
||||||
}
|
}
|
||||||
$scope.$destroy();
|
$scope.$destroy();
|
||||||
|
this.$element.triggerHandler !('$destroy');
|
||||||
}
|
}
|
||||||
|
|
||||||
prepareTransclusion(): angular.ILinkFn|undefined {
|
prepareTransclusion(): angular.ILinkFn|undefined {
|
||||||
|
@ -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: '<div *ngIf="!ng2Destroy"><ng1></ng1></div>'})
|
||||||
|
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('<ng2></ng2>');
|
||||||
|
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: '<div *ngIf="!ng2Destroy"><ng1></ng1></div>'})
|
||||||
|
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('<ng2></ng2>');
|
||||||
|
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', () => {
|
describe('linking', () => {
|
||||||
it('should run the pre-linking after instantiating the controller', async(() => {
|
it('should run the pre-linking after instantiating the controller', async(() => {
|
||||||
const adapter: UpgradeAdapter = new UpgradeAdapter(forwardRef(() => Ng2Module));
|
const adapter: UpgradeAdapter = new UpgradeAdapter(forwardRef(() => Ng2Module));
|
||||||
|
@ -3629,7 +3629,68 @@ withEachNg1Version(() => {
|
|||||||
ng2ComponentAInstance.destroyIt = true;
|
ng2ComponentAInstance.destroyIt = true;
|
||||||
$digest(adapter);
|
$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: '<ng2B *ngIf="!destroyIt"></ng2B>'})
|
||||||
|
class Ng2ComponentA {
|
||||||
|
destroyIt = false;
|
||||||
|
|
||||||
|
constructor() { ng2ComponentAInstance = this; }
|
||||||
|
}
|
||||||
|
|
||||||
|
@Component({selector: 'ng2B', template: '<ng1></ng1>'})
|
||||||
|
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(`<ng2-a></ng2-a>`);
|
||||||
|
|
||||||
|
bootstrap(platformBrowserDynamic(), Ng2Module, element, ng1Module).then(adapter => {
|
||||||
|
expect(elementDestroyListener).not.toHaveBeenCalled();
|
||||||
|
|
||||||
|
ng2ComponentAInstance.destroyIt = true;
|
||||||
|
$digest(adapter);
|
||||||
|
|
||||||
|
expect(elementDestroyListener).toHaveBeenCalledTimes(1);
|
||||||
});
|
});
|
||||||
}));
|
}));
|
||||||
|
|
||||||
|
Loading…
x
Reference in New Issue
Block a user