You are currently viewing NGRX with Angular: The Basics Explained with Examples

State management is a key challenge in modern frontend applications. As apps grow, managing shared data (like user sessions, API responses, or UI state) becomes harder. NGRX is a powerful state management library for Angular that implements the Redux pattern using RxJS observables.

In this tutorial, we’ll go through the basics of NGRX step by step, including setup and examples.


What is NGRX?

  • NGRX = Angular + Redux
  • Provides a centralized store for application state.
  • Uses actionsreducers, and selectors.
  • Built on RxJS observables (reactive streams).

In short: You dispatch an action, a reducer updates the state, and selectors allow you to read the state.


Step 1: Install NGRX

Run inside your Angular project:

PowerShell
ng add @ngrx/store
ng add @ngrx/effects
ng add @ngrx/store-devtools
  • @ngrx/store → Central store
  • @ngrx/effects → For handling async tasks (like API calls)
  • @ngrx/store-devtools → Debug state with Chrome/Edge DevTools

Step 2: Define the State

Let’s say we want to manage a list of books.

Create a file: src/app/store/book.model.ts

TypeScript
export interface Book {
  id: number;
  title: string;
  author: string;
}

export interface BookState {
  books: Book[];
}

Step 3: Define Actions

Actions represent what happened.
Example: Load books, add a book, remove a book.

File: src/app/store/book.actions.ts

TypeScript
import { createAction, props } from '@ngrx/store';
import { Book } from './book.model';

export const loadBooks = createAction('[Book] Load Books');

export const loadBooksSuccess = createAction(
  '[Book] Load Books Success',
  props<{ books: Book[] }>()
);

export const addBook = createAction(
  '[Book] Add Book',
  props<{ book: Book }>()
);

export const removeBook = createAction(
  '[Book] Remove Book',
  props<{ id: number }>()
);

Step 4: Create a Reducer

reducer decides how state changes when actions happen.

File: src/app/store/book.reducer.ts

TypeScript
import { createReducer, on } from '@ngrx/store';
import { BookState } from './book.model';
import * as BookActions from './book.actions';

export const initialState: BookState = {
  books: []
};

export const bookReducer = createReducer(
  initialState,
  on(BookActions.loadBooksSuccess, (state, { books }) => ({
    ...state,
    books: [...books]
  })),
  on(BookActions.addBook, (state, { book }) => ({
    ...state,
    books: [...state.books, book]
  })),
  on(BookActions.removeBook, (state, { id }) => ({
    ...state,
    books: state.books.filter(b => b.id !== id)
  }))
);

Step 5: Create Selectors

Selectors are used to read state efficiently.

File: src/app/store/book.selectors.ts

TypeScript
import { createSelector, createFeatureSelector } from '@ngrx/store';
import { BookState } from './book.model';

export const selectBookState = createFeatureSelector<BookState>('books');

export const selectAllBooks = createSelector(
  selectBookState,
  (state: BookState) => state.books
);

Step 6: Register Store in App Module

Open app.module.ts:

TypeScript
import { NgModule } from '@angular/core';
import { BrowserModule } from '@angular/platform-browser';
import { StoreModule } from '@ngrx/store';
import { bookReducer } from './store/book.reducer';
import { StoreDevtoolsModule } from '@ngrx/store-devtools';

import { AppComponent } from './app.component';

@NgModule({
  declarations: [AppComponent],
  imports: [
    BrowserModule,
    StoreModule.forRoot({ books: bookReducer }),
    StoreDevtoolsModule.instrument({ maxAge: 25 })
  ],
  bootstrap: [AppComponent]
})
export class AppModule {}

Step 7: Using Store in a Component

Example: Display books and add a new one.

File: src/app/app.component.ts

TypeScript
import { Component } from '@angular/core';
import { Store } from '@ngrx/store';
import { Observable } from 'rxjs';
import { Book } from './store/book.model';
import { selectAllBooks } from './store/book.selectors';
import { addBook, removeBook } from './store/book.actions';

@Component({
  selector: 'app-root',
  templateUrl: './app.component.html',
})
export class AppComponent {
  books$: Observable<Book[]>;

  constructor(private store: Store) {
    this.books$ = this.store.select(selectAllBooks);
  }

  addNewBook() {
    const newBook: Book = {
      id: Math.random(),
      title: 'New Angular Book',
      author: 'John Developer'
    };
    this.store.dispatch(addBook({ book: newBook }));
  }

  deleteBook(id: number) {
    this.store.dispatch(removeBook({ id }));
  }
}

File: src/app/app.component.html

TypeScript
<h2>Book List</h2>
<ul>
  <li *ngFor="let book of books$ | async">
    {{ book.title }} - {{ book.author }}
    <button (click)="deleteBook(book.id)">Remove</button>
  </li>
</ul>

<button (click)="addNewBook()">Add Random Book</button>

Step 8: Handling API Calls with Effects

Effects handle side effects like fetching data from an API.

File: src/app/store/book.effects.ts

TypeScript
import { Injectable } from '@angular/core';
import { Actions, createEffect, ofType } from '@ngrx/effects';
import { of } from 'rxjs';
import { map, mergeMap, catchError } from 'rxjs/operators';
import * as BookActions from './book.actions';

@Injectable()
export class BookEffects {
  constructor(private actions$: Actions) {}

  loadBooks$ = createEffect(() =>
    this.actions$.pipe(
      ofType(BookActions.loadBooks),
      mergeMap(() =>
        // Simulated API call
        of([
          { id: 1, title: 'Angular Basics', author: 'Jane Doe' },
          { id: 2, title: 'NgRx in Action', author: 'John Smith' }
        ]).pipe(
          map(books => BookActions.loadBooksSuccess({ books })),
          catchError(() => of({ type: '[Book API] Load Failed' }))
        )
      )
    )
  );
}

Register in app.module.ts:

TypeScript
import { EffectsModule } from '@ngrx/effects';
import { BookEffects } from './store/book.effects';

@NgModule({
  ...
  imports: [
    BrowserModule,
    StoreModule.forRoot({ books: bookReducer }),
    EffectsModule.forRoot([BookEffects]),
    StoreDevtoolsModule.instrument({ maxAge: 25 })
  ],
  ...
})
export class AppModule {}

Conclusion

In this tutorial, we covered the basics of NGRX:

  • Actions → describe what happened
  • Reducers → decide how state changes
  • Selectors → read state
  • Effects → handle async operations

With this foundation, you can now scale your Angular apps with predictable state management and better debugging tools.

Please follow and like us:

Leave a Reply