• To implement NgRx,
    Install package @ngrx/store: npm install --save @ngrx/store
    
    Now, we need to create one app level reducer.
    
    Create a “store” folder.
    Create a file say “app.reducer.ts”
    
    
    import { ActionReducerMap } from '@ngrx/store'; export interface AppState { } export const AppStoreReducer: ActionReducerMap = { };
    Here, We have created an interface “AppState”. We will add properties in it for different states. We have created AppStoreReducer. Which is of type ActionReducerMap. This will handle our mappings of different reducers. It is of type AppState. Now, we need to let our application know that we are using ngrx stores. So for that we need to include StoreModule in AppModule
    import { StoreModule } from '@ngrx/store';
    In imports,
    StoreModule.forRoot(fromApp.AppStoreReducer)
    So, all the states declared under AppStoreReducer are now initialized at time of application bootup. Suppose you want to replace auth service with ngrx states. In auth service, you can see that all authentication is just to set user context in the system. So, we need to manage state for the “user” model. Create a folder called “store” in auth. Create two files in it. auth.reducer.ts auth.actions.ts In auth.reducer.ts Initialize a default state. Note: Default state should be an object where you have a property user. It should be constant.
    const initialState = { user: null }
    Here, the user is of null type. Meaning it can hold null as default value. We can then assign it values as “User” model We need to understand that initialState should have a type, so we can use it outside of this reducer function. Lets define State (interface).
    export interface State { user: User; }
    Now, we know that initialState is of type State.
    const initialState: State = { user: null }
    Understand that reducer is nothing but a function, using which, we can call methods for different actions. Here, we want to maintain user state for the login & logout actions of Auth Service into the reducer first. Lets create a reducer function.
    export function AuthReducer() { return null; }
    Here, we are just returning null always. But, we need to return a state after each different action. So, we need 2 inputs here. state action
    export function AuthReducer(state, action) { return state; }
    Initially, we will have no state, so we need to set a default value to the state otherwise it will return null. Lets first provide a default value to the state. And that is our “initialState”
    export function AuthReducer(state = initialState, action) { return state; }
    Now, we need to know which action should return what state(updated state). We need to know which actions we want to reduce here. In our case, we just need login & logout actions for now. So, in auth.actions.ts Lets just define below things. Name of the actions
    export const LOGIN = 'LOGIN'; export const LOGOUT = 'LOGOUT';
    Classes which should be called
    export class Login implements Action { readonly type = LOGIN; constructor(public payload: User) {} } export class Logout implements Action { readonly type = LOGOUT; }
    Here, We have implemented Action from @ngrx/store We will need to create a property “type” which should be readonly. So no one can change its value outside of the class. We will need to create a constructor if we need to pass any data within the action otherwise it is not needed. Actions type to access these actions in the reducer. export type AuthActions = Login | Logout; We have all needed actions defined. So, now we can use it in the reducer.
    export function AuthReducer(state = initialState, action: AuthActions.AuthActions) { return state; }
    Now, we can use action and we can return updated state as per different actions.
    export function AuthReducer( state = initialState, action: AuthActions.AuthActions ) { switch (action.type) { case AuthActions.LOGIN: const user = action.payload; return { ...state, user }; case AuthActions.LOGOUT: return { ...state, user: null }; default: return state; } }
    Here, we are just updating the user in its current state. In login, we have updated the current state’s user.
    const user = action.payload; { ...state, user }
    This syntax means. Copy current state and just change user state which came from action.payload In logout, we have updated the current state’s user to null. { ...state, user: null } This syntax means. Copy current state and just change user state to null. We are done with the reducer and actions. Now, we can use it in the auth service. First of all, we need to declare it in the App Store. Our existing app store is like below.
    import { ActionReducerMap } from '@ngrx/store'; import * as fromShoppingList from '../shopping-list/store/shopping-list.reducer'; export interface AppState { shoppingList: fromShoppingList.State; } export const AppStoreReducer: ActionReducerMap = { shoppingList: fromShoppingList.ShoppingListReducer };
    Now, we need to add an auth reducer.
    import * as fromAuth from '../auth/store/auth.reducer';
    Now, we can use it in AppState interface and its implementation
    export interface AppState { shoppingList: fromShoppingList.State; auth: fromAuth.State; } export const AppStoreReducer: ActionReducerMap = { shoppingList: fromShoppingList.ShoppingListReducer, auth: fromAuth.AuthReducer };
    Let's use it in auth service. We will need to import 2 things. App store Auth Actions
    import * as fromApp from '../store/app.reducer'; import * as AuthActions from './store/auth.actions';
    To use the app store, we need to define it in constructor. Previously, our constructor looked like constructor(private httpClient: HttpClient, private router: Router) {} Now,
    constructor( private httpClient: HttpClient, private router: Router, private store: Store ) {}
    We added Store from @ngrx/store We gave it which state we wanted to use. I.e. fromApp.AppState Let's start with the easy one. Let's use a store in logout. Previously, our logout function looked like
    logOut() { this.user.next(null); localStorage.removeItem('userData'); if (this.tokenExpirationTimer) { clearTimeout(this.tokenExpirationTimer); } this.tokenExpirationTimer = null; this.router.navigate(['/login']); }
    Now,
    logOut() { this.store.dispatch(new AuthActions.Logout()); localStorage.removeItem('userData'); if (this.tokenExpirationTimer) { clearTimeout(this.tokenExpirationTimer); } this.tokenExpirationTimer = null; this.router.navigate(['/login']); }
    Here, we have removed using BehaviorSubject and dispatched an action for logout. Same way, let's implement it for login. Before:
    autoLogin() { const data: { email: string; id: string; _token: string; _tokenExpirationDate: string; } = JSON.parse(localStorage.getItem('userData')); if (!data) { return; } const userData = new User( data.email, data.id, data._token, new Date(data._tokenExpirationDate) ); if (userData.token) { this.user.next(userData); const expirationDuration = new Date(data._tokenExpirationDate).getTime() - new Date().getTime(); this.autoLogout(expirationDuration); } }
    After:
    autoLogin() { const data: { email: string; id: string; _token: string; _tokenExpirationDate: string; } = JSON.parse(localStorage.getItem('userData')); if (!data) { return; } const userData = new User( data.email, data.id, data._token, new Date(data._tokenExpirationDate) ); if (userData.token) { this.store.dispatch(new AuthActions.Login(userData)); const expirationDuration = new Date(data._tokenExpirationDate).getTime() - new Date().getTime(); this.autoLogout(expirationDuration); } }
    We removed using this.user.next(userData); and dispatched login action. this.store.dispatch(new AuthActions.Login(userData)); Before:
    private handleAuthentication( email: string, localId: string, idToken: string, expiresIn: string ) { const expirationDateTime = new Date( new Date().getTime() + +expiresIn * 1000 ); const user = new User(email, localId, idToken, expirationDateTime); this.user.next(user); localStorage.setItem('userData', JSON.stringify(user)); this.autoLogout(+expiresIn * 1000); }
    After:
    private handleAuthentication( email: string, localId: string, idToken: string, expiresIn: string ) { const expirationDateTime = new Date( new Date().getTime() + +expiresIn * 1000 ); const user = new User(email, localId, idToken, expirationDateTime); this.store.dispatch(new AuthActions.Login(user)); localStorage.setItem('userData', JSON.stringify(user)); this.autoLogout(+expiresIn * 1000); }
    We can now remove user = new BehaviorSubject(null); As, we are using it in the interceptor. Lets fix it there too. Before:
    export class AuthInterceptor implements HttpInterceptor { constructor(private authService: AuthService) {} intercept(req: HttpRequest, next: HttpHandler) { return this.authService.user.pipe( take(1), exhaustMap(userData => { if (userData) { const modifiedReq = req.clone({ params: new HttpParams().set('auth', userData.token) }); return next.handle(modifiedReq); } return next.handle(req); }) ); } }
    After:
    export class AuthInterceptor implements HttpInterceptor { constructor(private store: Store) {} intercept(req: HttpRequest, next: HttpHandler) { return this.store.select('auth').pipe( take(1), map(authState => { return authState.user; }), exhaustMap(userData => { if (userData) { const modifiedReq = req.clone({ params: new HttpParams().set('auth', userData.token) }); return next.handle(modifiedReq); } return next.handle(req); }) ); } }
    Here, we have replaced auth service with an app store in constructor.
    constructor(private store: Store) {}
    Now, we need to get state from the store this.store.select('auth') Note: Our state has “user” as property. So we need to add mapping for using it.
    map(authState => { return authState.user; }),
    Now, in exhaustMap we do not need to make any changes. Same way, we need to make changes in the header component as well. Before:
    constructor( private dataStorageService: DataStorageService, private authService: AuthService ) {}
    After:
    constructor( private dataStorageService: DataStorageService, private authService: AuthService, private store: Store ) {}
    In ngOnInit Before:
    ngOnInit(): void { this.authSubscriber = this.authService.user.subscribe(res => { this.isAuthenticated = !!res; }); if (this.isAuthenticated) { this.dataStorageService.fetchRecipes().subscribe(); } }
    After
    ngOnInit(): void { this.authSubscriber = this.store.select('auth').pipe(map(authState => authState.user)).subscribe(res => { this.isAuthenticated = !!res; }); if (this.isAuthenticated) { this.dataStorageService.fetchRecipes().subscribe(); } }
    Same way, we need to make changes in the header component as well. Effects To install package npm install --save @ngrx/effects In auth service, we do have code which can be moved to effects. To create effects, follow below steps: Create auth.effects.ts file under auth/store folder.
    export class AuthEffects {}
    Lets, add it in app.module.ts In imports, we need to import this class to be used.
    EffectsModule.forRoot([AuthEffects])
    It will from
    import { EffectsModule } from '@ngrx/effects'; import { AuthEffects } from './auth/store/auth.effects';
    Now, we need to handle the effects of different actions here. So, first lets import Actions from @ngrx/effects also tell application that this is an effect by adding @Effect() Note: This should be injectable class.
    import { Actions } from '@ngrx/effects'; import { Injectable } from '@angular/core'; @Injectable() export class AuthEffects { constructor(private actions: Actions) {} }
    Lets create a new action in app.actions.ts for which we want to add effects.
    export const LOGIN_START = 'LOGIN_START'; export class LoginStart implements Action { readonly type = LOGIN_START; constructor(public payload: { email: string; password: string }) {} } export type AuthActions = Login | Logout | LoginStart;
    Now, in effects, we can use this action.
    import { Actions, Effect, ofType } from '@ngrx/effects'; import * as AuthActions from './auth.actions'; import { Injectable } from '@angular/core'; @Injectable() export class AuthEffects { @Effect() authLogic = this.actions.pipe(ofType(AuthActions.LOGIN_START)); constructor(private actions: Actions) {} }
    In the pipe, we can then add which type of code to handle. We will add it in switchMap
    authLogic = this.actions.pipe( ofType(AuthActions.LOGIN_START), switchMap((authData: AuthActions.LoginStart) => {}) );
    Now, we can move http call of auth service login to login start effect.
    import { Actions, Effect, ofType } from '@ngrx/effects'; import * as AuthActions from './auth.actions'; import { switchMap } from 'rxjs/operators'; import { AuthResponseData } from '../auth.service'; import { environment } from 'src/environments/environment'; import { HttpClient } from '@angular/common/http'; import { Injectable } from '@angular/core'; @Injectable() export class AuthEffects { @Effect() authLogic = this.actions.pipe( ofType(AuthActions.LOGIN_START), switchMap((authData: AuthActions.LoginStart) => { return this.httpClient.post( 'https://identitytoolkit.googleapis.com/v1/accounts:signInWithPassword?key=' + environment.firebaseApiKey, { email: authData.payload.email, password: authData.payload.password, returnSecureToken: true } ); }) ); constructor(private actions: Actions, private httpClient: HttpClient) {} }
    We simply copied the login http request code to switch map. Added necessary imports. Now, we need to add pipe for error and success.
    authLogic = this.actions.pipe( ofType(AuthActions.LOGIN_START), switchMap((authData: AuthActions.LoginStart) => { return this.httpClient .post( 'https://identitytoolkit.googleapis.com/v1/accounts:signInWithPassword?key=' + environment.firebaseApiKey, { email: authData.payload.email, password: authData.payload.password, returnSecureToken: true } ) .pipe( catchError(error => { return of(); }), map(resData => { return null; }) ); }) );
    Here, we first added a pipe which handles catchError for any error and map for mapping success response.
    map((resData: AuthResponseData) => { const expirationDateTime = new Date( new Date().getTime() + + resData.expiresIn * 1000 ); const user = new User(authData.payload.email, resData.localId, resData.idToken, expirationDateTime); return new AuthActions.Login(user); })
    Now, to handle error, Lets first add action in auth.actions
    export const LOGIN_FAILED = 'LOGIN_FAILED'; export class LoginFailed implements Action { readonly type = LOGIN_FAILED; constructor(public payload: string) {} } export type AuthActions = Login | Logout | LoginStart | LoginFailed;
    Let's add in reducer state
    authError: string;
    In initialState,
    authError: null
    Let's add 2 actions
    case AuthActions.LOGIN_FAILED: return { ...state, authError: action.payload, user: null }; case AuthActions.LOGIN_START: return { ...state, authError: null, user: null };
    We can also move loading state to auth reducer
    import { User } from '../user.model'; import * as AuthActions from './auth.actions'; export interface State { user: User; authError: string; loading: boolean; } const initialState: State = { user: null, authError: null, loading: false }; export function AuthReducer( state = initialState, action: AuthActions.AuthActions ) { switch (action.type) { case AuthActions.LOGIN: const user = action.payload; return { ...state, authError: null, user, loading: false }; case AuthActions.LOGOUT: return { ...state, authError: null, user: null, loading: false }; case AuthActions.LOGIN_FAILED: return { ...state, authError: action.payload, user: null, loading: false }; case AuthActions.LOGIN_START: return { ...state, authError: null, user: null, loading: true }; default: return state; } }
    In auth.actions we can now use it in error
    import { Actions, ofType, Effect } from '@ngrx/effects'; import * as AuthActions from './auth.actions'; import { switchMap, catchError, map, tap } from 'rxjs/operators'; import { AuthResponseData } from '../auth.service'; import { environment } from 'src/environments/environment'; import { HttpClient, HttpErrorResponse } from '@angular/common/http'; import { of } from 'rxjs'; import { User } from '../user.model'; import { Injectable } from '@angular/core'; import { Router } from '@angular/router'; @Injectable() export class AuthEffects { @Effect() authLogic = this.actions.pipe( ofType(AuthActions.LOGIN_START), switchMap((authData: AuthActions.LoginStart) => { return this.httpClient .post( 'https://identitytoolkit.googleapis.com/v1/accounts:signInWithPassword?key=' + environment.firebaseApiKey, { email: authData.payload.email, password: authData.payload.password, returnSecureToken: true } ) .pipe( catchError((error: HttpErrorResponse) => { let errorMessage = 'Error Occured!'; if (!error.error || !error.error.error) { return of(new AuthActions.LoginFailed(errorMessage)); } switch (error.error.error.message) { case 'EMAIL_EXISTS': errorMessage = 'This email already exist!'; break; case 'INVALID_PASSWORD': errorMessage = 'Invalid Password!'; break; case 'EMAIL_NOT_FOUND': errorMessage = 'Invalid Email!'; break; } return of(new AuthActions.LoginFailed(errorMessage)); }), map((resData: AuthResponseData) => { const expirationDateTime = new Date( new Date().getTime() + +resData.expiresIn * 1000 ); const user = new User( authData.payload.email, resData.localId, resData.idToken, expirationDateTime ); return new AuthActions.Login(user); }) ); }) ); @Effect({ dispatch: false }) authSuccess = this.actions.pipe( ofType(AuthActions.LOGIN), tap(() => { this.router.navigate(['/recipes']); }) ); constructor( private actions: Actions, private httpClient: HttpClient, private router: Router ) {} }
    Here, we copied code of handleError from auth service to effects in catchError. Now, we can use it in app.component.ts First, we need to import store. Before:
    constructor(private authService: AuthService, private router: Router>) {}
    After:
    constructor(private authService: AuthService, private router: Router, private store: Store) {}
    We can now replace this with actions
    authObserv = this.authService.logIn(email, password); this.store.dispatch(new AuthActions.LoginStart({ email, password }));
    We also used authSuccess to handle navigation. In ngOnInit we can subscribe to this
    ngOnInit(): void { this.store.select('auth').subscribe(authState => { this.isLoading = authState.loading; this.error = authState.authError; }); }
    We do not need authSubscribe after login so we can now move it in the signup case. Which we will move to effects soon. To sum up with, We have created a class of effects. We added 2 effects. authLogin authSuccess authLogin effect handles, LoginStart and Login actions. authSuccess effect handles, navigation after successful login. In authLogin, we called api by http request. We have handled success and error. In success, we returned the user object to Login action. In error, we returned the error to LoginFailed action. For this, we have added 2 new actions. LoginStart LoginFailed LoginStart is called from the component. Login is called from effects if success. LoginFailed is called from effects if error.
0 Years in
Operation
0 Loyal
Clients
0 Successful
Projects

Words from our clients

 

Tell Us About Your Project

We’ve done lot’s of work, Let’s Check some from here