---
description: >-
  In this section we will use NgRx's router store library to listen to url or
  router events as actions in our effects.
---

# 24. Router store

## 1. npm i @ngrx/router-store

```
npm i @ngrx/router-store
```

## 2.  Add to AppModule

* Add `StoreRouterConnectingModule` to the AppModule.
* Often it is needed to create  your own router serilaizer but we will skip this step.

{% code title="src/app/app.module.ts" %}
```typescript
import { BrowserModule } from '@angular/platform-browser';
import { NgModule } from '@angular/core';
import { HttpClientModule } from '@angular/common/http';
import { RouterModule } from '@angular/router';
import { HttpClientInMemoryWebApiModule } from 'angular-in-memory-web-api';
import { StoreModule } from '@ngrx/store';
import { StoreDevtoolsModule } from '@ngrx/store-devtools';
import { EffectsModule } from '@ngrx/effects';
import { StoreRouterConnectingModule } from '@ngrx/router-store';

import { HomeComponent } from './home/containers/home/home.component';
import { AppComponent } from './app.component';
import { InMemoryDataService } from './app.db';
import { reducer } from './state/spinner/spinner.reducer';
import { environment } from '../environments/environment.prod';

@NgModule({
  declarations: [AppComponent, HomeComponent],
  imports: [
    BrowserModule,
    RouterModule.forRoot([
      { path: '', pathMatch: 'full', redirectTo: 'home' },
      { path: 'home', component: HomeComponent },
      { path: 'event', loadChildren: './event/event.module#EventModule' }
    ]),
    HttpClientModule,
    HttpClientInMemoryWebApiModule.forRoot(InMemoryDataService, {
      delay: 1000
    }),
    StoreModule.forRoot({ spinner: reducer }),
    EffectsModule.forRoot([]),
    StoreDevtoolsModule.instrument({
      name: 'NgRx Demo App',
      logOnly: environment.production
    }),
    StoreRouterConnectingModule.forRoot(),
  ],
  providers: [],
  bootstrap: [AppComponent]
})
export class AppModule {}


```
{% endcode %}

![Image: Showing router events as actions in the devtools](https://3531511677-files.gitbook.io/~/files/v0/b/gitbook-legacy-files/o/assets%2F-LLqc06ABPywqowC_y8E%2F-LMIJyZICoFVrliZp0rF%2F-LMILcZziwmgLLSGD4TP%2Fimage.png?alt=media\&token=00407668-09e8-4df6-8097-b117dad8738f)

## 3. Add a select box to EventComponent to update URL

* Add select box element.

{% code title="src/app/event/event.component.html" %}
```markup
<app-add-attendee (addAttendee)="addAttendee($event)"></app-add-attendee>
<select name="Guests filter" (change)="navigate($event.target.value)">
  <option [value]="'all'">All</option>
  <option [value]="'withGuests'">With Guests</option>
  <option [value]="'withoutGuests'">Without Guests</option>
</select>
<app-event-list *ngIf="!(spinner$ | async)" [attendees]="attendees$ | async"></app-event-list>


```
{% endcode %}

## 4. Add navigate method to component

* Add a navigate method to the component to update the URL.
* Delete dispatched LoadAttendees action.

{% code title="src/app/event/event.component.ts" %}
```typescript
import { Component, OnInit } from '@angular/core';
import { Observable } from 'rxjs';
import { Store, select } from '@ngrx/store';

import { Attendee } from '../../../models';
import { EventService } from '../../services/event.service';
import {
  StartSpinner,
  StopSpinner
} from '../../../state/spinner/spinner.actions';
import { getSpinner } from '../../../state/spinner/spinner.selectors';
import {
  LoadAttendees,
  AddAttendee
} from '../../state/attendees/attendees.actions';
import { EventState } from '../../state';
import { getAttendees } from '../../state/attendees/attendees.selectors';
import { Router } from '@angular/router';

@Component({
  selector: 'app-event',
  templateUrl: './event.component.html',
  styleUrls: ['./event.component.scss']
})
export class EventComponent implements OnInit {
  spinner$: Observable<boolean>;
  attendees$: Observable<Attendee[]>;

  constructor(
    private store: Store<EventState>,
    private eventService: EventService,
    private router: Router
  ) {}

  ngOnInit() {
    this.attendees$ = this.store.pipe(select(getAttendees));
  }

  addAttendee(attendee: Attendee) {
    this.store.dispatch(new AddAttendee(attendee));
  }

  navigate(filterBy: string) {
    this.router.navigateByUrl(`/event?filterBy=${filterBy}`);
  }
}

```
{% endcode %}

## 5. Add next action creator for filterBy property

* Add new `AttendeesActionTypes` for `FilterBy`.
* Add new class for `FilterBy`
* Add new `AttendeesActions` for `FilterBy`.

{% code title="src/app/event/state/attendees/attendees.actions.ts" %}
```typescript
import { Action } from '@ngrx/store';
import { Attendee } from '../../../models';
export enum AttendeesActionTypes {
  LoadAttendees = '[Attendees Page] Load Attendees',
  LoadAttendeesSuccess = '[Attendees Page] Load Attendees Success',
  LoadAttendeesFail = '[Attendees Page] Load Attendees Fail',
  AddAttendee = '[Attendee Page] Add Attendee',
  AddAttendeeSuccess = '[Attendee API] Add Attendee Success',
  AddAttendeeFail = '[Attendee API] Add Attendee Fail',
  FilterBy = '[Attendee Page] FilterBy'
}
export class LoadAttendees implements Action {
  readonly type = AttendeesActionTypes.LoadAttendees;
}
export class LoadAttendeesSuccess implements Action {
  readonly type = AttendeesActionTypes.LoadAttendeesSuccess;
  constructor(public payload: Attendee[]) {}
}
export class LoadAttendeesFail implements Action {
  readonly type = AttendeesActionTypes.LoadAttendeesFail;
  constructor(public payload: any) {}
}

export class AddAttendee implements Action {
  readonly type = AttendeesActionTypes.AddAttendee;
  constructor(public payload: Attendee) {}
}

export class AddAttendeeSuccess implements Action {
  readonly type = AttendeesActionTypes.AddAttendeeSuccess;
  constructor(public payload: Attendee) {}
}

export class AddAttendeeFail implements Action {
  readonly type = AttendeesActionTypes.AddAttendeeFail;
  constructor(public payload: any) {}
}

export class FilterBy implements Action {
  readonly type = AttendeesActionTypes.FilterBy;
  constructor(public payload: string) {}
}

export type AttendeesActions =
  | FilterBy
  | AddAttendee
  | AddAttendeeSuccess
  | AddAttendeeFail
  | LoadAttendees
  | LoadAttendeesSuccess
  | LoadAttendeesFail;

```
{% endcode %}

## 6. Add an effect to listen to router events

* Add an effect listening to `RouterEvents` that begin with `/events`.

{% code title="src/app/event/state/attendees.effects.ts" %}
```typescript
import { Injectable } from '@angular/core';
import { Actions, Effect } from '@ngrx/effects';
import { ofType } from '@ngrx/effects';
import { switchMap, map, catchError, filter, tap } from 'rxjs/operators';
import { of } from 'rxjs';

import { EventService } from '../../services/event.service';
import {
  AttendeesActionTypes,
  LoadAttendees,
  LoadAttendeesSuccess,
  LoadAttendeesFail,
  AddAttendee,
  AddAttendeeSuccess,
  AddAttendeeFail,
  FilterBy
} from './attendees.actions';
import { Attendee } from '../../../models';
import { ROUTER_NAVIGATION } from '@ngrx/router-store';
import { RouterNavigationAction } from '@ngrx/router-store';

@Injectable()
export class AttendeesEffects {
  constructor(private actions$: Actions, private eventService: EventService) {}

  @Effect()
  getAttendees$ = this.actions$.pipe(
    ofType(AttendeesActionTypes.LoadAttendees),
    switchMap((action: LoadAttendees) =>
      this.eventService.getAttendees().pipe(
        map((attendees: Attendee[]) => new LoadAttendeesSuccess(attendees)),
        catchError(error => of(new LoadAttendeesFail(error)))
      )
    )
  );

  @Effect()
  addAttendee$ = this.actions$.pipe(
    ofType(AttendeesActionTypes.AddAttendee),
    switchMap((action: AddAttendee) =>
      this.eventService.addAttendee(action.payload).pipe(
        map((attendee: Attendee) => new AddAttendeeSuccess(attendee)),
        catchError(error => of(new AddAttendeeFail(error)))
      )
    )
  );

  @Effect()
  loadDiaryHealthActions$ = this.actions$.pipe(
    ofType(ROUTER_NAVIGATION),
    map((r: RouterNavigationAction) => ({
      url: r.payload.routerState.url,
      filterBy: r.payload.routerState.root.queryParams['filterBy']
    })),
    filter(({ url, filterBy }) => url.startsWith('/event')),
    map(({ filterBy }) => new FilterBy(filterBy))
  );
}

```
{% endcode %}

## 7. Update reducer to have new filterBy

* Add switch case to set filterBy property.

{% code title="src/app/event/state/attendees/attendees.reducer.ts" %}
```typescript
import { EntityState, EntityAdapter, createEntityAdapter } from '@ngrx/entity';

import { AttendeesActions, AttendeesActionTypes } from './attendees.actions';
import { Attendee } from '../../../models';

export interface State extends EntityState<Attendee> {
  loading: boolean;
  error: any;
  filterBy: string;
}

const adapter: EntityAdapter<Attendee> = createEntityAdapter<Attendee>();

export const intitalState: State = adapter.getInitialState({
  loading: false,
  error: null,
  filterBy: 'all'
});

export function reducer(state = intitalState, action: AttendeesActions): State {
  switch (action.type) {
    case AttendeesActionTypes.LoadAttendees: {
      return adapter.removeAll({
        ...state,
        loading: false,
        error: null
      });
    }

    case AttendeesActionTypes.LoadAttendeesSuccess: {
      return adapter.addAll(action.payload, {
        ...state,
        loading: false,
        error: null
      });
    }

    case AttendeesActionTypes.LoadAttendeesFail: {
      return adapter.removeAll({
        ...state,
        loading: false,
        error: action.payload
      });
    }

    case AttendeesActionTypes.AddAttendeeSuccess: {
      return adapter.addOne(action.payload, { ...state, error: null });
    }

    case AttendeesActionTypes.AddAttendeeFail: {
      return { ...state, error: action.payload };
    }

    case AttendeesActionTypes.FilterBy: {
      return { ...state, filterBy: action.payload };
    }

    default: {
      return state;
    }
  }
}

export const {
  selectIds,
  selectEntities,
  selectAll,
  selectTotal
} = adapter.getSelectors();

```
{% endcode %}

## 8. Add new selector for filtering attendees.

* Add selector to get filterBY state property.
* Use two getAttendees and getFilterBy to filter the list.

{% code title="src/app/event/state/attendees/attendees.selectors.ts" %}
```typescript
---------- ABBREVIATED CODE SNIPPET ----------

export const getFilterBy = createSelector(
  getAttendeeState,
  state => state.filterBy
);

export const getFilteredAttendees = createSelector(
  getAttendees,
  getFilterBy,
  (attendees, filterBy) =>
    attendees.filter(
      attendee =>
        filterBy === 'all'
          ? true
          : filterBy === 'withGuests'
            ? attendee.guests >= 1
            : attendee.guests === 0
    )
);

---------- ABBREVIATED CODE SNIPPET ----------
```
{% endcode %}

## 9. Use new selector in EventComponent

* Swap getAttendees selector for getFilteredAttendees selector.

{% code title="src/app/event/containers/event/event.component.ts" %}
```typescript
---------- ABBREVIATED CODE SNIPPET ----------

  ngOnInit() {
    this.attendees$ = this.store.pipe(select(getFilteredAttendees));
    this.store.dispatch(new LoadAttendees());
  }

---------- ABBREVIATED CODE SNIPPET ----------
```
{% endcode %}

## StackBlitz Link

{% embed url="https://stackblitz.com/github/duncanhunter/angular-and-ngrx-demo-app/tree/24-router-store" %}
Web Link: Link to the demo app running in StackBlitz
{% endembed %}

