angular: Unable to overwrite ngDestroy inside the component
🐞 bug report
Affected Package
The issue is caused by package @angular/....Is this a regression?
Yes, the previous version in which this bug was not present was: 8.2
Description
Please take a look here for a source code: https://github.com/xtephan/ng-destroy-test/blob/master/src/app/destroyable/destroyable.component.ts
When the DestroyableComponent
is being destroyed, the ngOnDestroy
method as defined on line 16 is called, instead of as defined on line 11.
If inspecting the component using the devtools, ngOnDestroy
method points to being located on line 11:
🔬 Minimal Reproduction
https://github.com/xtephan/ng-destroy-test
The master
branch contains ng9, where bug can be seen present.
The ng8
branch contains ng8, where the bug was not present.
Run the app with ng serve
and access http://localhost:4200
. In the webpage, press the toggle button to destroy the inner component.
The expected result would to see this.ngOnDestroy called
outputed in the console.
Actual behaviour is that DestroyableComponent.ngOnDestroy called
is being outputed.
🔥 Exception or Error
None
🌍 Your Environment
Angular Version:
_ _ ____ _ ___
/ \ _ __ __ _ _ _| | __ _ _ __ / ___| | |_ _|
/ △ \ | '_ \ / _` | | | | |/ _` | '__| | | | | | |
/ ___ \| | | | (_| | |_| | | (_| | | | |___| |___ | |
/_/ \_\_| |_|\__, |\__,_|_|\__,_|_| \____|_____|___|
|___/
Angular CLI: 9.1.3
Node: 12.4.0
OS: darwin x64
Angular: 9.1.3
... animations, cli, common, compiler, compiler-cli, core, forms
... language-service, platform-browser, platform-browser-dynamic
... router
Ivy Workspace: Yes
Package Version
-----------------------------------------------------------
@angular-devkit/architect 0.901.3
@angular-devkit/build-angular 0.901.3
@angular-devkit/build-optimizer 0.901.3
@angular-devkit/build-webpack 0.901.3
@angular-devkit/core 9.1.3
@angular-devkit/schematics 9.1.3
@ngtools/webpack 9.1.3
@schematics/angular 9.1.3
@schematics/update 0.901.3
rxjs 6.5.5
typescript 3.8.3
webpack 4.42.0
Anything else relevant?
About this issue
- Original URL
- State: closed
- Created 4 years ago
- Reactions: 10
- Comments: 15 (6 by maintainers)
I work with @xtephan, on the same project, so i can attempt to answer that.
We have a custom subsink, that overwrites the
ngDestroy
method to allow us to cleanup subscriptions when a component is destroyed. The subsink works by assigning to a property directly on the object, and keeping a reference to the old method, so we can call the onDestroy. So in relation to what is said on the linked issues, specifically here: https://github.com/angular/angular/issues/7270#issuecomment-201137617, the onDestroy method is there in the original class definition, as can also be seen in the sample provided.Subsink code:
hooks.ts:
The way the hook is implemented, we can layer multiple on destroy hooks, without them getting in the way of each other, since they will all be called.
The subsink is then used in a component like this:
An alternative we have discussed internally as a solution for this specific regression, is making the SubSink an injectable service (With a unique instance for each component instance), and then relying on ngOnDestroy being called on the service. This however then requires that we explicitly pass the SubSink in the
provider
array of every single component we have. To avoid forgetting to do that, we will need to write a custom lint rule to check that. All in all, quite a bit of work to get around Angular not “observing” the normal rules of how “methods” works in javascript, and instead relying on the method only being defined on the prototype.@juanmendes Here you go: https://gist.github.com/zlepper/df282c06bafe46342d5b43f4e049726a It needs to be compiled and such from the TS version, but i figure you can handle that part? 😃
Additionally adding subscriptions to other subscriptions also causes those subscriptions to be cancelled, if the first subscription completes (At least it did last i tried).
@juanmendes We ended up using a component scoped service instead:
Turns out Angular calls the OnDestroy method on services if they are scoped. So we add the service to our component providers if we need it, then dependency inject it. Then we additionally have a ts-lint rule to make sure you actually remember to add it to providers, and doesn’t accidently use the parent’s subsink. It’s a bit more involved, but we still get the behavior we want, even though it’s nowhere near as nice as it used to be.