Why Router Data Should Live in Effects

A key benefit of ngrx is keeping side effects out of the component layer.

When a component needs route information, the typical approach is to reach for ActivatedRoute directly. For instance, pulling an id from the URL often looks like this:

export class MyComponent {
  id: string;
  id$ = this.activatedRoute.params.pipe(
    map(params => params.id),
    tap(id => this.id = id),
  );
}

With @ngrx/effects, we can centralize that logic. The challenge is figuring out how to read the route parameter from within an effect.

A flawed approach

One might try injecting ActivatedRoute into an effect, but that won't work. As noted in the [official docs](https://angular.io/api/router/ActivatedRoute), it only exists for components rendered inside a router outlet.

The Router service is another possibility, but it doesn't offer a clear or convenient way to extract a specific URL parameter.

The right tool here is the selectRouteParam selector from @ngrx/router-store.

Let's walk through a practical example (a stackblitz demo is linked at the end).

The correct setup

Here’s how to declare the effect:

  setCurrentCourse$ = createEffect(() => this.store$.pipe(
    select(selectRouteParam('id')))
      .pipe(
        map((id: string) => setIds({id})), // dispatch a new action to set the selected id
      ),
    {dispatch: true},
  );

With that in place, the component can stay remarkably lean:

import { Store, select } from '@ngrx/store';
import { Component } from '@angular/core';

@Component({
  selector: 'app-root',
  template: `{{selectedId$ | async | json}}`,
})
export class HomeComponent {
  selectedId$ = this.store.pipe(select((state: any) => state.featureName.selectedId));
  constructor(
    private store: Store,
  ) { }
}

What this unlocks

This simple pattern for retrieving a param is just the starting point. The real advantage comes when you need to act on that value—say, fetching data based on the id. You can chain a switchMap inside the effect, use the id to call your API, and let the component simply subscribe to the resulting state. No extra boilerplate.

If this helped, share some appreciation here or reach out on Twitter @adamgenshaft.