Introduction

Ngrx is a framework for Angular that allows you to create reactive applications. NgRx is based on the concept of the Redux JavaScript framework.

State

The Ngrx Store consists of several features, each of which has its own state as well as its actions, reducers and effects.

Ngrx State Management Lifecycle

Lifecycle of the “State” in Ngrx

State

Here the current state of the application is stored.
The state can be read in the application but not changed, since it is immutable. The state can only be overwritten by a reducer with a new version of the state.

Action

Actions are the triggers for changes and either lead to the state being renewed by a reducer or to an effect being executed. A action can be triggered for various reasons, such as loading a page, clicking a component or executing an effect.

Reducer

Reducers are pure functions that change the state of the application. They are triggered by actions and create the new state based on the current state and any parameters of the action. As they are pure functions, no side effects, such as loading data, may occur.

Effects

As most applications won’t work without using side effects, Ngrx offers effects for this purpose. These are also triggered by an action and may then trigger a side effect, such as a database query or a REST query. The data fetched may not be written directly to the state but must be passed on to a reducer with an action, and only then will the state be updated.

Feature erstellen

To illustrate the functionality of Ngrx, here is a short example.
The below example is just one way to use Ngrx, for other possibilities and further information it is worth consulting the official documentation.

Installation

  ng add @ngrx/store
ng add @ngrx/effects
  

Erstellung

session.model.ts

export interface SessionState {
  username: string | null;
  active: boolean;
  loading: boolean;
  errors: string[];
}

session.action.ts

import { createActionGroup, emptyProps, props } from '@ngrx/store';

export const SessionAction = createActionGroup({
  source: 'Session',
  events: {
    authenticate: props<{ username: string; password: string; }>(),
    authenticateSuccess: props<{ username: string }>(),
    authenticateFailure: props<{ errors: string[] }>(),
    logout: emptyProps(),
    logoutSuccess: emptyProps(),
    logoutFailure: props<{ errors: string[] }>(),
  }
});

session.reducer.ts

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

import { SessionAction } from './session.action';
import { SessionState } from './session.model';

const initialState: SessionState = {
  username: null,
  active: false,
  loading: false,
  errors: []
}

export const SessionFeature = createFeature({
  name: 'Session',
  reducer: createReducer(
    initialState,
    on(
      SessionAction.authenticate,
      (state): SessionState => ({
        ...state,
        loading: true
      })
    ),
    on(
      SessionAction.authenticateSuccess,
      (state, { username }): SessionState => ({
        username,
        active: true
        loading: false,
        errors: []
      })
    ),
    on(
      SessionAction.authenticateFailure,
      (state, { errors }): SessionState => ({
        ...state,
        loading: false,
        errors
      })
    ),
    on(
      SessionAction.logout,
      (state): SessionState => ({
        ...state,
        loading: true
      })
    ),
    on(
      SessionAction.logoutSuccess,
      (state): SessionState => ({
        username: null,
        active: false
        loading: false,
        errors: []
      })
    ),
    on(
      SessionAction.logoutFailure,
      (state): SessionState => ({
        ...state,
        loading: false,
        errors
      })
    ),
  )
});

session.effect.ts

import { Injectable } from '@angular/core';
import { Actions, createEffect, ofType } from '@ngrx/effects';
import { catchError, exhaustMap, of } from 'rxjs';

import { AuthenticationService } from '@core/service/authentication.service';
import { SessionAction } from './session.action';

@Injectable()
export class SessionEffect {
  public authenticate$ = createEffect(() =>
    this.actions$.pipe(
      ofType(SessionAction.authenticate),
      exhaustMap(() =>
        this.authenticationService.authenticate({ username: action.username, password: action.password }).pipe(
          map((response) =>
            SessionAction.authenticateSuccess({ username: response.username })
          ),
          catchError((response) =>
            of(SessionAction.authenticateFailure({ errors: response.error.errors }))
          )
        )
      )
    )
  );

  public logout$ = createEffect(() =>
    this.actions$.pipe(
      ofType(SessionAction.logout),
      exhaustMap(() =>
        this.authenticationService.logout().pipe(
          map((response) =>
            SessionAction.logoutSuccess()
          ),
          catchError((response) =>
            of(SessionAction.logoutFailure({ errors: response.error.errors }))
          )
        )
      )
    )
  );

  constructor(
    private actions$: Actions,
    private authenticationService: AuthenticationService
  ) {}
}

Registrierung

app.config.ts

...
import { provideState, provideStore } from '@ngrx/store';
import { provideEffects } from '@ngrx/effects';
...
import { SessionFeature } from '@+store/session.reducer';
import { SessionEffect } from '@+store/session.effect';
...
export const appConfig: ApplicationConfig = {
  providers: [
    ...
    provideStore(),
    provideState(SessionFeature),
    provideEffects(SessionEffect)
  ],
  ...
}

Nutzung

...
import { Store } from '@ngrx/store';
import { Observable } from 'rxjs';
...
import { SessionAction } from '@+store/session.action';
import { SessionFeature } from '@+store/session.reducer';
...
@Component({
  selector: 'app-session',
  standalone: true,
  imports: [],
  templateUrl: './session.component.html',
  styleUrl: './session.component.scss'
})
export class SessionComponent {
  public username$: Observable<string | null>;

  constructor(private store: Store) {
    // Read value from the State
    this.username$ = this.store.select(SessionFeature.selectUsername);
  }

  public login(username: string, password: string): void {
    // Dispatch an action
    this.store.dispatch(SessionAction.authenticate({ username, password }));
  }
}

Resources

Ngrx Documentation

Last updated 08 Jan 2025, 17:05 +0100 . history