Creating Redirect Effects page
Learn how to create NgRx Effects that redirect the user.
Quick Start: You can checkout this branch to get your codebase ready to work on this section.
Overview
Add
loginSuccess$
Effect toLoginEffects
to navigate to dashboard page.Add
logoutSuccess$
Effect toLoginEffects
to navigate to login page.
Problem 1: Create loginSuccess$
Effect to Handle Navigating to Dashboard Page
LoginEffects
should use Router
to navigate to the dashboard page at path /dashboard
using an Effect called loginSuccess$
.
P1: What you need to know
Although it’s common for Effects to dispatch another Action after handling a side-effect, there is a way to update the configuration for an Effect to never dispatch an Action instead. This is useful when a side-effect resolves and there’s no need to trigger another side-effect or update state.
To understand this better, let’s take a deeper dive into the createEffect()
helper function:
createEffect()
takes two arguments:
source
- A function which returns anObservable
.config
(optional) - APartial<EffectConfig>
to configure the Effect. By default,dispatch
option is true anduseEffectsErrorHandler
is true.
And if we look a little deeper, here is the type definition of the EffectConfig
interface:
/**
* Configures an Effect created by `createEffect()`.
*/
export interface EffectConfig {
/**
* Determines if the Action emitted by the Effect is dispatched to the store.
* If false, Effect does not need to return type `Observable<Action>`.
*/
dispatch?: boolean;
/**
* Determines if the Effect will be resubscribed to if an error occurs in the main Actions stream.
*/
useEffectsErrorHandler?: boolean;
}
By default, the dispatch
option is set to true
, but if we set it to false
, the Effect doesn’t have to end with an Action being dispatched:
// Note: This example code is not part of our application repo or solution
import { Injectable } from '@angular/core';
import { Observable, exhaustMap } from 'rxjs/operators';
import { Actions, createEffect, ofType } from '@ngrx/effects';
import * as ContactActions from './contact.actions';
@Injectable()
export class ContactEffects {
submitSuccess$ = createEffect(() => {
return this.actions$.pipe(
ofType(ContactActions.submitSuccess),
exhaustMap(({ confirmationNumber }) =>
// call `showModal()`, but doesn’t `map` to some Action
this.showModal(confirmationNumber)
)
);
}, { dispatch: false });
constructor(private actions$: Actions) {}
private showModal(confirmationNumber: number): Observable<void> {/* ... */}
}
P1: Solution
src/app/store/login/login.effects.ts
// src/app/store/login/login.effects.ts
import { Injectable } from '@angular/core';
import { Actions, createEffect, ofType } from '@ngrx/effects';
import { catchError, map, exhaustMap } from 'rxjs/operators';
import { of } from 'rxjs';
import * as LoginActions from './login.actions';
import { LoginService } from 'ngx-learn-ngrx';
import { Router } from '@angular/router';
@Injectable()
export class LoginEffects {
login$ = createEffect(() => {
return this.actions$.pipe(
ofType(LoginActions.login),
exhaustMap(({ username, password }) =>
this.loginService.login({ username, password }).pipe(
map(({ userId, token }) =>
LoginActions.loginSuccess({ userId, username, token })
),
catchError((error: unknown) =>
of(
LoginActions.loginFailure({
errorMsg: this.getErrorMessage(error),
})
)
)
)
)
);
});
loginSuccess$ = createEffect(
() => {
return this.actions$.pipe(
ofType(LoginActions.loginSuccess),
exhaustMap(() => this.router.navigate(['dashboard']))
);
},
{ dispatch: false }
);
logout$ = createEffect(() => {
return this.actions$.pipe(
ofType(LoginActions.logout),
exhaustMap(() =>
this.loginService.logout().pipe(
map(() => LoginActions.logoutSuccess()),
catchError((error: unknown) =>
of(
LoginActions.logoutFailure({
errorMsg: this.getErrorMessage(error),
})
)
)
)
)
);
});
constructor(
private actions$: Actions,
private loginService: LoginService,
private router: Router
) {}
private getErrorMessage(error: unknown): string {
if (error instanceof Error) {
return error.message;
}
return String(error);
}
}
Problem 2: Create logoutSuccess$
Effect to Handle Navigating to Login Page
LoginEffects
should use Router
to navigate to the dashboard page at path /
using an Effect called loginSuccess$
.
P2: Solution
src/app/store/login/login.effects.ts
// src/app/store/login/login.effects.ts
import { Injectable } from '@angular/core';
import { Actions, createEffect, ofType } from '@ngrx/effects';
import { catchError, map, exhaustMap } from 'rxjs/operators';
import { of } from 'rxjs';
import * as LoginActions from './login.actions';
import { LoginService } from 'ngx-learn-ngrx';
import { Router } from '@angular/router';
@Injectable()
export class LoginEffects {
login$ = createEffect(() => {
return this.actions$.pipe(
ofType(LoginActions.login),
exhaustMap(({ username, password }) =>
this.loginService.login({ username, password }).pipe(
map(({ userId, token }) =>
LoginActions.loginSuccess({ userId, username, token })
),
catchError((error: unknown) =>
of(
LoginActions.loginFailure({
errorMsg: this.getErrorMessage(error),
})
)
)
)
)
);
});
loginSuccess$ = createEffect(
() => {
return this.actions$.pipe(
ofType(LoginActions.loginSuccess),
exhaustMap(() => this.router.navigate(['dashboard']))
);
},
{ dispatch: false }
);
logout$ = createEffect(() => {
return this.actions$.pipe(
ofType(LoginActions.logout),
exhaustMap(() =>
this.loginService.logout().pipe(
map(() => LoginActions.logoutSuccess()),
catchError((error: unknown) =>
of(
LoginActions.logoutFailure({
errorMsg: this.getErrorMessage(error),
})
)
)
)
)
);
});
logoutSuccess$ = createEffect(
() => {
return this.actions$.pipe(
ofType(LoginActions.logoutSuccess),
exhaustMap(() => this.router.navigate(['']))
);
},
{ dispatch: false }
);
constructor(
private actions$: Actions,
private loginService: LoginService,
private router: Router
) {}
private getErrorMessage(error: unknown): string {
if (error instanceof Error) {
return error.message;
}
return String(error);
}
}
Verify Implementation
At this point, you should be able to login on the login page:
Note that authenication DOES NOT persist after a page refresh. This means that after you make code changes while serving the application, you will be signed out and will need to login again. Remember that the login page is located at
/
.
The LoginService.login()
method will throw an error if any of these cases are not met:
password
must be at least 6 characters.username
must be at least 3 characters.username
must be alphanumeric including hyphens or underscores.
When one of these requirements aren’t met, an error is thrown and an error is logged in the console in red text.
Wrap-up: By the end of this section, your code should match this branch. You can also compare the code changes for our solution to this section on GitHub or you can use the following command in your terminal:
git diff origin/create-redirect-effects