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
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'));
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,
) {}
}