Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Efficient way to get route parameter in Angular

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:

  • Reduce boilerplate code
  • Make the code more readable
  • Get rid of subscriptions

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

like image 595
szab.kel Avatar asked Sep 20 '18 07:09

szab.kel


4 Answers

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();
}
like image 162
tom van green Avatar answered Nov 14 '22 02:11

tom van green


Try this:

constructor(private route: ActivatedRoute) {}

ngOnInit() {
    const id = this.route.snapshot.params['id'];
}
like image 29
tano Avatar answered Nov 14 '22 03:11

tano


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();
  }
}
like image 1
szab.kel Avatar answered Nov 14 '22 02:11

szab.kel


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

like image 1
Ploppy Avatar answered Nov 14 '22 01:11

Ploppy