How can I use router Observables more efficiently? If I need to load a single route parameter for example (let's say we have a route like /some-resource/:id
), I need to subscribe to the router event, then to the route params to get the value. This requires two subscriptions and two unsubscribes.
I would like to:
Sample
export class SomeComponent implements OnInit, OnDestroy {
private routerSub: Subscription;
private routeSub: Subscription;
someResource: Observable<SomeResourceType>;
constructor(private someService: SomeService,
private route: ActivatedRoute,
private router: Router) {
this.routerSub = this.router.events.subscribe((event) => {
if (event instanceof NavigationEnd) {
this.routeSub = this.route.params.subscribe((params) => {
if (params['id']) {
this.someResource = this.someService.findById(params['id']);
// will access the resource using async pipe later
}
});
}
});
}
ngOnInit(): void {
}
ngOnDestroy(): void {
this.routerSub.unsubscribe();
this.routeSub.unsubscribe();
}
}
The event subscription is needed to refresh the data if for some reason the component is not destroyed by angular, but still loaded using a different route param stackblitz example: https://stackblitz.com/edit/angular-router-basic-example-695kpb
You can use activated route for that.
constructor(route: ActivatedRoute) {
this.id$ = route.params
.pipe(pluck('id'));
}
You can use pluck. pluck('id')
is basically the same as map(value => value.id)
. If you don't want to have a stream but the actual value, you can do the same and subscribe to it. But if you do that, don't forget to unsubscribe from the observable. You can do this with the take until operator.
id;
private _destroyed$ = new Subject<any>();
constructor(route: ActivatedRoute) {
route.params
.pipe(
takeUntil(this._destroyed$),
pluck('id')
).subscribe(id => this.id = id);
}
ngOnDestroy() {
this._destroyed$.next();
this._destroyed$.complete();
}
Try this:
constructor(private route: ActivatedRoute) {}
ngOnInit() {
const id = this.route.snapshot.params['id'];
}
As long as no one posts a better solution, here is mine:
I defined a RouterHelperService
, which makes this process somewhat easier. One of the problem is, if you try to inject the ActivatedRoute
instance directly in your service, you will be missing the params, so you need to pass it from your component to the service.
import { Injectable } from '@angular/core';
import { ActivatedRoute, NavigationEnd, Router } from '@angular/router';
import { filter, flatMap, map } from 'rxjs/operators';
import { Observable } from 'rxjs';
@Injectable({
providedIn: 'root'
})
export class RouterHelperService {
constructor(private router: Router) {
}
onNavigationEndReadParamByKey(route: ActivatedRoute, key: string): Observable<string> {
return this.router.events.pipe(
filter(event => event instanceof NavigationEnd),
flatMap(() => {
return route.params.pipe(
filter(params => params[key]),
map(params => params[key])
);
})
);
}
}
This way, in my component, I can call it with a single call and a single subscription.
export class SomeComponent implements OnInit, OnDestroy {
private routeSub: Subscription;
someResource: Observable<SomeResourceType>;
constructor(private someService: SomeService,
private route: ActivatedRoute) {
this.routeSub = this.routerHelper.onNavigationEndReadParamByKey(this.route, 'id').subscribe((id) => {
this.someResource = this.someService.findById(+id); //+id to convert it from a string to a number
});
}
ngOnInit(): void {
}
ngOnDestroy(): void {
this.routeSub.unsubscribe();
}
}
Fastest way, which does not support component reload:
constructor(
route: ActivatedRoute,
) {
const myParam = route.snapshot.params.idThread;
}
Using the paramMap and the 'new' RxJs syntax, which support component reload:
constructor(
route: ActivatedRoute,
) {
route.paramMap.subscribe({
next: params => {
const myParam = params.get('myParam');
}
});
}
NB: unsubscribing is not mandatory in this case and should not lead to memory leaks
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With