Serengeti logo BLACK white bg w slogan
Menu

Basic NgRx Implementation

Teo Kolić, Mid Software Developer
17.01.2023.

Ngrx is a library that provides a way to manage state in Angular applications. It is based on the concept of Redux, a popular state management library for JavaScript applications. In this blog, we will go over the basic implementation of ngrx in an Angular application.

To use ngrx in an Angular application, we will need to install it via npm.

npm install @ngrx/store

Once ngrx is installed, we can start using it. The first step is to define the state that we want to manage. We can do this by creating a state interface.

export interface AppState {

  count: number;

  user: string;  

}

Next, we need to create a reducer function that will handle the state transitions. A reducer function takes in the current state and an action and returns the new state.

import { createReducer, on } from '@ngrx/store';

const initialState: AppState = { count: 0, user: 'John Doe' };

const reducer = createReducer(

  initialState,

  on(increment, state => ({ count: state.count + 1 })),

  on(decrement, state => ({ count: state.count - 1 }))

);

export function counterReducer(state: AppState | undefined, action: Action) {

  return reducer(state, action);

}

Once we have defined our state and reducer functions, we can create a store by importing the StoreModule from @ngrx/store and adding it to our Angular application's root module.

import { StoreModule } from '@ngrx/store';

@NgModule({

  imports: [

    StoreModule.forRoot({ count: counterReducer })

  ]

})

export class AppModule {}

Now that we have a store set up, we can use it to manage the state of our application. To do this, we can inject the Store service into our Angular components or services and use the select method to retrieve the current state or the dispatch method to dispatch actions to the store.

To use the action creators, we will need to import the createAction function from @ngrx/store and use it to define the actions.

import { createAction } from '@ngrx/store';

export const increment = createAction('INCREMENT');

export const decrement = createAction('DECREMENT');

export const loadUser = createAction('LOAD_USER');

export const loadUserSuccess = createAction(

'LOAD_USER_SUCCESS',

props<{user: User}>

);

Now we can use the action creators in our Angular components or services to dispatch actions to the store.

import { Component } from '@angular/core';

import { Store } from '@ngrx/store';

import { increment, decrement } from './actions';

@Component({

  selector: 'app-root',

  template: `

    <button (click)="increment()">Increment</button>

    <button (click)="decrement()">Decrement</button>

    <p>Count: {{ count$ | async }}</p>

  `

})

export class AppComponent {

  count$: Observable<number>;

  constructor(private store: Store<AppState>) {

    this.count$ = this.store.select(selectUserState());

  }

  increment() {

    this.store.dispatch(increment());

  }

  decrement() {

    this.store.dispatch(decrement());

  }

}

To get data from the store, we need to create selectors

import { createSelector } from '@ngrx/store';

const selectUserState = (state: AppState) => state.user;

export const selectUser = createSelector(

  selectUserState,

  (state: UserState) => state.user

);

Ngrx effects are a way to isolate side effects in an Angular application. They allow us  to dispatch actions in response to certain events, such as an HTTP request or a user interaction.

Here is an example of an ngrx effect that listens for a LOAD_USER action and dispatches a LOAD_USER_SUCCESS action when the HTTP request to the server is successful.

import { Injectable } from '@angular/core';

import { Actions, createEffect, ofType } from '@ngrx/effects';

import { concatMap, map } from 'rxjs/operators';

import { HttpClient } from '@angular/common/http';

@Injectable()

export class UserEffects {

  loadUser$ = createEffect(() => {

    return this.actions$.pipe(

      ofType(loadUser),

      concatMap(action =>

        this.http.get<User>(`/api/users/${action.id}`).pipe(

          map(user => (this.store.dispatch(loadUserSuccess({user})))

        )

      )

    );

  });

  constructor(private actions$: Actions, private http: HttpClient) {}

}

To use this effect, we will need to import the EffectsModule from @ngrx/effects and add it to our Angular application's root module.

@NgModule({

  imports: [

    StoreModule.forRoot({ count: reducer }),

    EffectsModule.forRoot([UserEffects])

Now, whenever a LOAD_USER action is dispatched, the loadUser$ effect will be triggered and it will make an HTTP request to the server and dispatch a LOAD_USER_SUCCESS action with the retrieved user data.

import { Component } from '@angular/core';

import { Store } from '@ngrx/store';

...

@Component({

  selector: 'app-user',

  template: `

    <button (click)="loadUser()">Load User</button>

    <p *ngIf="user$ | async as user">{{ user.name }}</p>

  `

})

export class UserComponent {

  constructor(private store: Store<AppState>) {}

  get user$(): Observable<User>{

      return this.store.select(selectUser);

  }

  loadUser() {

    this.store.dispatch({ type: 'LOAD_USER', id: 1 });

  }

}

That's it! We have successfully implemented ngrx in our Angular application. Ngrx offers many more features such as middleware, dev tools, and action creators, which can be used to further enhance the state management of your application. For example, there is a Redux add-on for Google Chrome through which we can watch state changes in real time and watch replays of dispatched actions and state changes after each action, which is very handy for debugging.

Source: https://ngrx.io/

Let's do business

The project was co-financed by the European Union from the European Regional Development Fund. The content of the site is the sole responsibility of Serengeti ltd.
cross