Howto navigate in Angular 2 when using ngrx store

9.6k views Asked by At

I'm using ngrx store (4.x) together with Angular 4. I use effects to make CRUD operations on the backend, like the example below which adds a Task on the backend API.

Effect:

  @Effect()
  addTask: Observable<Action> = this.actions$
    .ofType(LeadAction.ADD_TASK)
    .map((action: LeadAction.AddTaskAction) => action.payload)
    .switchMap((task: TaskViewModel) => {
      return this.leadApi.leadAddTask(task.LeadId, task)
        .map((taskResult: TaskViewModel) => {
          return new LeadAction.AddTaskSuccessAction(taskResult);
        })
        .catch((e: any) => of(new LeadAction.AddTaskFailureAction(e)));
    });

TaskEditComponent :

  onSave(): void {
    this.store.dispatch(new AddTaskAction(this.task));

    // **** NAVIGATE TO PAGE TaskListComponent or OverviewComponent ON SUCCESS
    // OR
    // **** NAVGIATE TO PAGE Y ON ERROR
  }

Question: In my component I need to navigate to different pages and I struggle now where to put this logic?

Especially when I think about following scenarios, where the TaskEditComponent is 'called' by different Components:

Should navigate back to TaskListComponent:

OverviewComponent->TaskListComponent->TaskEditComponent back to List

Should navigate back to OverviewComponent:

OverviewComponent->TaskEditComponent

1

There are 1 answers

8
Heehaaw On BEST ANSWER

Using ngrx, it makes sense to let your store handle the router state as well, preserving the redux paradigm. Then you would simply dispatch a router action in the effect in reaction to your success actions.

This has the added benefit of being able to 'time travel' the routes as well as the rest of the app state.

Fortunately, there is already an implementation of router-store integration ready to be used.


You could do something like this (just a guideline, enhance to your needs):

app.module

import { StoreRouterConnectingModule, routerReducer } from '@ngrx/router-store';
import { App } from './app.component';

@NgModule({
  imports: [
    BrowserModule,
    StoreModule.forRoot({ routerReducer: routerReducer }),
    RouterModule.forRoot([
      // ...
      { path: 'task-list', component: TaskListComponent },
      { path: 'error-page', component: ErrorPageComponent }
    ]),
    StoreRouterConnectingModule
  ],
  bootstrap: [App]
})
export class AppModule { }

task.effects

import { go } from '@ngrx/router-store';

@Effect()
addTask: Observable<Action> = this.actions$
  .ofType(LeadAction.ADD_TASK_SUCCESS)
  .map((action: LeadAction.AddTaskSuccessAction) => action.payload)
  .map((payload: any) => go('/task-list')); // use payload to construct route options

@Effect()
addTask: Observable<Action> = this.actions$
  .ofType(LeadAction.ADD_TASK_FAILURE)
  .mapTo(go('/error-page'));

Update using NGRX v8+ with latest features:

AppModule:

import { StoreRouterConnectingModule, routerReducer } from '@ngrx/router-store';
import { AppComponent } from './app.component';

@NgModule({
  imports: [
    BrowserModule,
    StoreModule.forRoot({ routerReducer }),
    RouterModule.forRoot([
      // ...
      { path: 'task-list', component: TaskListComponent },
      { path: 'error-page', component: ErrorPageComponent }
    ]),
    StoreRouterConnectingModule.forRoot(),
  ],
  bootstrap: [AppComponent],
})
export class AppModule {}

TaskEffects:

@Injectable()
export class TaskEffects {
  readonly addTaskSuccess$ = createEffect(() =>
    this.actions$.pipe(
      ofType(LeadAction.ADD_TASK_SUCCESS),
      tap(() => this.router.navigate(['task-list'])),
    ),
    { dispatch: false },
  );
  readonly addTaskFailure$ = createEffect(() =>
    this.actions$.pipe(
      ofType(LeadAction.ADD_TASK_FAILURE),
      tap(() => this.router.navigate(['error-page'])),
    ),
    { dispatch: false },
  );

  constructor(
    private readonly actions$: Actions,
    private readonly router: Router,
  ) {}
}