Reputation: 2991
I have recently learnt to avoid memory leak, I need to unsubscribe in my components. To do this I implement OnDestroy
but this has caused an issue when navigating.
ERROR Error: Uncaught (in promise): TypeError: Cannot read property 'unsubscribe' of undefined TypeError: Cannot read property 'unsubscribe' of undefined
If I don't unsubscribe navigation completes successfully.
I was under the impression that ngOnDestroy
only happens once everything else has completed, obviously that is not the case.
I'm obviously doing something really wrong, but I cant figure out what How do I unsubscribe when navigating away from a page?
export class PropertyFormMapperComponent implements OnInit, OnDestroy {
private _routeSubscription: any;
private _getPropertySubscription: any;
constructor(
private _route: ActivatedRoute,
private _propertyFormFileService: PropertyFormFileService,
private _router: Router
) { }
ngOnInit(): void {
this._routeSubscription = this._route.params.subscribe(params => {
this._id = params['id'];
this._getPropertySubscription = this._propertyService.getProperty(this._id).subscribe(property => {
...do stuff...
});
});
}
ngOnDestroy(): void {
this._routeSubscription.unsubscribe();
this._getPropertySubscription.unsubscribe();
}
private onCreate(event: any): void {
this._propertyService.postProperty(property, 1).subscribe(
response => {
...do stuff...
this._router.navigate(['home']);
},
error => {
...other stuff...
}
);
}
}
Upvotes: 2
Views: 1895
Reputation: 4122
Here, this one should work for you. I combined route params stream and getProperty stream using concatMap
and projection function. You can read more about it in rxjs docs: rxjs concatMap
I also use takeUntil
which unsubscribes from the stream it is piped into when other stream emits. rxjs takeUntil
So when componentDestroyed$
observable emits, because takeUntil
took it as a param, takeUntil
will then unsubscribe from this._route.params
and this._propertyService.getProperty
streams.
I also added takeUntil
to onCreate
method, since you also subscribe in there.
takeUntil
is an alternative to keeping track of all your Subuscription
s and manually unsubscribing from them in ngOnDestroy
. With takeUntil
we achieve the same thing, but don't have to clutter the component with subscription properties.
You can take it one step up and have a base class that all your component classes inherit from --> then you don't have to keep componentDestroyed$
in every component and can skip ngOnDestroy()
part too:
export abstract class BaseComponent implements OnDestroy {
protected componentDestroyed$: Subject<void> = new Subject<void>();
ngOnDestroy() {
this.componentDestroyed$.next();
this.componentDestroyed$.complete();
}
}
And your components now will be inheriting from BaseComponent
:
export class PropertyFormMapperComponent extends BaseComponent implements OnInit, OnDestroy
Your code fixed:
import {Subscription} from 'rxjs';
import {tap, map, concatMap, takeUntil} from 'rxjs/operators';
export class PropertyFormMapperComponent implements OnInit, OnDestroy {
componentDestroyed$: Subject<void> = new Subject<void>();
constructor(
private _route: ActivatedRoute,
private _propertyFormFileService: PropertyFormFileService,
private _router: Router
) { }
ngOnInit() {
this._route.params
.pipe(
map(params => params['id']),
tap(id => this._id = id),
concatMap(id => this._propertyService.getProperty(id))
takeUntil(this.componentDestroyed$),
tap(property => {
...do stuff...
... side effects are handled in tap functions
})
)
.subscribe();
}
private onCreate(event) {
this._propertyService.postProperty(property, 1)
.pipe(
takeUntil(this.componentDestroyed$)
)
.subscribe(
response => {
...do stuff...
this._router.navigate(['home']);
},
error => {
...other stuff...
}
);
}
ngOnDestroy() {
this.componentDestroyed$.next();
this.componentDestroyed$.complete();
}
}
Upvotes: 1