ngx-modialog
ngx-modialog copied to clipboard
ngOnDestroy is never called when using TemplateRef modal
-
I'm submitting a ... [x] bug report [ ] feature request [ ] question about the decisions made in the repository
-
What is the current behavior?
Plunkr showing another component embedded in the modal using the <template>
approach.
https://embed.plnkr.co/dkhsP5ztQUM8Zq7ym4j0/
Open the console. You will see that ngOnInit
is called each time the modal is opened, but ngOnDestroy
is never called. This leads to strange behaviour in the child component because subscriptions to other services are not unsubscribed.
- What is the expected behavior?
When a modal is closed / dismissed I would expect the child components of the modal to also be destroyed using the standard ngOnDestroy
method.
If this is feature rather than a bug could you explain how we should destroy child components please?
I've found that I can subscribe to the onDestroy
observable of DialogRef
inside my child component. Is that the correct way to do this?
this.dialogRef.onDestroy.subscribe(() => {
// destroy child component here?
});
A bit more investigation has shown this is causing memory leaks.
You can see it happening here, on a small scale, with the Plunkr above:
It's much more of a problem in my real app where my modals contain complex logic.
I've also found that ngOnDestroy
doesn't even fire when I destroy the parent component that contains the <template>
. Note: You can't do this in the Plunkr because the parent in this case is the main app component.
So somehow its getting detached and lost in memory somewhere, and I think it is a problem with angular2-modal rather than Angular itself.
I created another Plunkr, with pure Angular, which embeds and destroys a child component using different techniques. In the console you will see that in this case ngOnDestroy
fires correctly.
https://plnkr.co/edit/ouK4sS2bjnL5AoIgHDAD?p=preview
Ahhhaaa progress...
It seems I can call the clear()
method on the defaultViewContainer
object, then I see ngOnDestroy
firing in my child component.
onClick() {
// open the modal
this.modal.open(this.customModalRef, overlayConfigFactory(BSModalContext))
.then((dialog: DialogRef<BSModalContext>) => {
// keep reference to dialog
this.dialog = dialog;
return dialog.result;
})
.then(() => {
console.log('closed');
// destroy when closed
this.destroyModal();
})
.catch(() => {
console.log('dismissed');
// destroy when dismissed
this.destroyModal();
});
}
private destroyModal () {
if (this.dialog) {
this.dialog.overlay.defaultViewContainer.clear();
this.dialog = null;
}
}
Updated plunker:
https://plnkr.co/edit/6l8sDxh92KdDqncgtRyX?p=preview
Is this the correct approach?
A side effect of this technique is that the close animation no longer occurs, because the contents are immediately destroyed. Perhaps if it was handled internally it could happen after the animation complete callback?
My solution to this problem:
export class MyComponent implements OnInit {
private _vcRef: ViewContainerRef;
.
.
.
constructor(overlay: Overlay,
vcRef: ViewContainerRef,
public modal: Modal) {
this._vcRef = vcRef;
overlay.defaultViewContainer = vcRef;
}
.
.
.
public openModalExclusao(_parcelamento: Parcelamento) {
this.parcelamento = _parcelamento;
return this.modal
.open(MotivoModalComponent, overlayConfigFactory({ parcelamento: this.parcelamento }, BSModalContext))
.then((dialog: DialogRef<BSModalContext>) => {
this.dialog = dialog;
return dialog.result;
})
.then((result: any) => {
this.destroyModal();
})
.catch((err: any) => {
this.destroyModal();
});
}
public destroyModal () {
if (this.dialog) {
this.dialog.overlay.defaultViewContainer = this._vcRef;
this.dialog = null;
}
}
}
In the constructor of my compoment, I assign a "_vcRef" variable of type "ViewContainerRef" or "vcRef".
this._vcRef = vcRef;
In the destroyModal () method I assign the reference of "_vcRef" to "defaultViewContainer".
this.dialog.overlay.defaultViewContainer = this._vcRef;
Solved my problem.