Angular Using Store And Effects Complete Guide

 Last Update:2025-06-22T00:00:00     .NET School AI Teacher - SELECT ANY TEXT TO EXPLANATION.    8 mins read      Difficulty-Level: beginner

Understanding the Core Concepts of Angular Using Store and Effects

Angular Using Store and Effects: Detailed Explanation and Important Information

Understanding the Store

At the heart of NgRx is the Store, which serves as a single, immutable source of truth for your entire application. The Store maintains the state tree, which includes the entire state of the application at any given time.

Key Concepts:

  1. State: Represents the data of the application at a certain point in time.
  2. Actions: Plain JavaScript objects that describe changes to the state.
  3. Reducers: Pure functions that process actions and return the new state.

Why Use Store?

  • Predictable State: Provides a centralized approach to manage state, reducing bugs related to shared mutable data.
  • Time Travel Debugging: Facilitates the tracking of state changes, which is invaluable for debugging and testing.
  • Developer Tool Integration: Leverages browser extensions to inspect and manipulate state.

Implementing Store:

  1. Install NgRx Store:
    npm install @ngrx/store
    
  2. Create State Interface and Initial State:
    export interface AppState {
      user: UserState;
    }
    
    export const initialState: AppState = {
      user: { id: 0, name: '' },
    };
    
  3. Define Actions:
    export const loadUser = createAction('[User Page] Load User');
    export const loadUserSuccess = createAction('[User API] Load User Success', props<{ user: UserState }>());
    
  4. Create Reducers:
    const userReducer = createReducer(
      initialState.user,
      on(loadUserSuccess, (state, { user }) => user)
    );
    
    export const reducers: ActionReducerMap<AppState> = {
      user: userReducer,
    };
    
  5. Provide Store Module:
    @NgModule({
      imports: [
        StoreModule.forRoot(reducers, {
          runtimeChecks: {
            strictStateImmutability: true,
            strictActionImmutability: true,
          },
        }),
      ],
    })
    export class AppModule {}
    
  6. Dispatch Actions and Select State:
    import { Store } from '@ngrx/store';
    
    constructor(private store: Store<AppState>) {}
    
    loadUser() {
      this.store.dispatch(loadUser());
    }
    
    userName$ = this.store.select('user', 'name');
    

Understanding the Effects

Effects facilitate side effects such as data fetching and state persistence. They can dispatch new actions if necessary, and are built on top of RxJS observables.

Key Concepts:

  1. Actions: Effects listen for dispatched actions and perform tasks based on these actions.
  2. Observables: Actions are treated as streams of data, allowing for complex operations such as filtering, mapping, and combining.
  3. Operators: RxJS Operators are used to process and transform streams of actions.

Why Use Effects?

  • Decoupling: Separates side effects from components, improving clarity and maintainability.
  • Reusability: Encapsulates logic for side effects, making it easier to reuse and test.
  • Concurrency: Manages concurrent operations gracefully, preventing race conditions and errors.

Implementing Effects:

  1. Install NgRx Effects:

Online Code run

🔔 Note: Select your programming language to check or run code at

💻 Run Code Compiler

Step-by-Step Guide: How to Implement Angular Using Store and Effects

Step 1: Set Up Your Angular Project

First, you need to create an Angular project. If you don't have Angular CLI installed yet, you can install it using npm.

npm install -g @angular/cli
ng new my-ng-app
cd my-ng-app

Step 2: Install NgRx Store and Effects

NgRx is a state management system for Angular apps. @ngrx/store and @ngrx/effects are the two main packages we'll be using here. Along with these, @ngrx/store-devtools is useful for debugging.

ng add @ngrx/store
ng add @ngrx/effects
ng add @ngrx/store-devtools

When prompted, choose to enable development mode and include the store devtools in production for debugging purposes.

Step 3: Define the State Model

The state in NgRx is defined as an object that holds various slices of state. For this example, let's assume you're building a simple app that manages a list of todos.

Create a file called todo.state.ts inside a store folder.

export interface TodoState {
  todos: string[];
}

Step 4: Create Actions

Actions represent events that happen in your application. Create a file called todo.actions.ts.

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

export const addTodo = createAction(
  '[Todo Component] Add Todo',
  props<{ todo: string }>()
);

export const removeTodo = createAction(
  '[Todo Component] Remove Todo',
  props<{ index: number }>()
);

Step 5: Prepare Reducers

Reducers specify how the application's state changes in response to actions. Create a file called todo.reducer.ts.

import { createReducer, on } from '@ngrx/store';
import { addTodo, removeTodo } from './todo.actions';

import { TodoState } from './todo.state';

export const initState: TodoState = { todos: [] };

export const todoReducer = createReducer(
  initState,
  on(addTodo, (state, { todo }) => ({
    ...state,
    todos: [...state.todos, todo]
  })),
  on(removeTodo, (state, { index }) => ({
    ...state,
    todos: state.todos.filter((_, i) => i !== index)
  }))
);

Step 6: Create Effect for Asynchronous Calls

Effects allow you to handle side effects in an NgRx-based application (for example, HTTP requests). You'll need an effect to fetch data asynchronously if necessary. For simplicity, let's mock fetching data.

Create a file called todo.effect.ts.

import { Injectable } from '@angular/core';
import { Actions, createEffect, ofType } from '@ngrx/effects';
import { TodoState } from './todo.state';
import { addTodo, removeTodo } from './todo.actions';
import { map, mergeMap, switchMap, of } from 'rxjs';

@Injectable()
export class TodoEffects {
  loadTodos$ = createEffect(() =>
    this.actions$.pipe(
      ofType('[Todo Component] Load Todos'),
      mergeMap(() =>
        of(['Buy milk', 'Read book']).pipe(
          map(todos => addTodo({ todo: todos[0] }))
        )
      )
    )
  );

  constructor(private actions$: Actions) {}
}

Step 7: Register Store Module & Effects

Now, configure the StoreModule and EffectsModule for your application.

Edit your app.module.ts:

import { BrowserModule } from '@angular/platform-browser';
import { NgModule } from '@angular/core';

import { StoreModule } from '@ngrx/store';
import { EffectsModule } from '@ngrx/effects';
import { StoreDevtoolsModule } from '@ngrx/store-devtools';

import { AppComponent } from './app.component';
import { todoReducer } from './store/todo.reducer';
import { TodoEffects } from './store/todo.effect';

@NgModule({
  declarations: [
    AppComponent
  ],
  imports: [
    BrowserModule,
    StoreModule.forRoot({ todos: todoReducer }),
    EffectsModule.forRoot([TodoEffects]),
    StoreDevtoolsModule.instrument(),
  ],
  providers: [],
  bootstrap: [AppComponent]
})
export class AppModule { }

Step 8: Selectors

Selectors are functions used to retrieve data from the application's state. Create a file todo.selectors.ts.

import { createSelector, createFeatureSelector } from '@ngrx/store';
import { TodoState } from '../store/todo.state';

const selectTodosState =
  createFeatureSelector<TodoState>('todos');

export const selectTodos = createSelector(
  selectTodosState,
  (state: TodoState) => state.todos
);

Step 9: Dispatch Actions from Components

Now you have everything set up, but you still need to dispatch actions from your components. Let’s do this in app.component.ts.

First, make sure to provide Store and Actions:

// app.component.ts

import { Component } from '@angular/core';
import { Store, select } from '@ngrx/store';
import { Observable } from 'rxjs';
import { TodoState } from './store/todo.state';
import { addTodo, removeTodo } from './store/todo.actions';
import { selectTodos } from './store/todo.selectors';

@Component({
  selector: 'app-root',
  templateUrl: './app.component.html',
  styleUrls: ['./app.component.css']
})
export class AppComponent {
  todos$: Observable<string[]> = this.store.pipe(select(selectTodos));

  constructor(private store: Store<{ todos: TodoState }>) {}

  addNewTodo(input: HTMLInputElement) {
    this.store.dispatch(addTodo({ todo: input.value }));
    input.value = '';
  }

  deleteTodo(index: number) {
    this.store.dispatch(removeTodo({ index }));
  }
}

Step 10: Update the Template

Finally, update the template to handle user interactions based on the component logic we just created.

Edit app.component.html:

<div style="text-align:center">
  <h1>NgRx Todo List</h1>
  <input #newTodo (keyup.enter)="addNewTodo(newTodo)" placeholder="Add a new Todo"/>
  
  <button (click)="addNewTodo(newTodo)">Add Todo</button>
  
  <ul>
    <li *ngFor="let todo of todos$ | async; let i = index">
      {{ todo }}
      <button (click)="deleteTodo(i)">Delete</button>
    </li>
  </ul>
</div>

Run the Application

You can now run your application to see it in action:

ng serve

Navigate to http://localhost:4200; you should be able to add, load, and delete todos.

Summary:

In this example, we walked through setting up an Angular project with NgRx Store and Effects. We defined an initial state, actions, reducers, and effects. We also covered how to dispatch actions and subscribe to changes from the store in the components.

Top 10 Interview Questions & Answers on Angular Using Store and Effects

Top 10 Questions and Answers on Angular Using Store and Effects

1. What are NgRx Store and Effects, and how do they work together in Angular projects?

  • NgRx Store is the central data store that holds all the application state data as a single immutable object tree. It provides a predictable state container managed by actions. This makes state changes easier to track and debug.

  • NgRx Effects is used for handling side effects and async operations within Angular apps. It listens to actions dispatched to the store and responds with additional actions. Effects can interact with APIs, handle external resources, and perform other async tasks.

Together, NgRx Store and Effects enable a clean separation of concerns where Store handles the synchronous state management, and Effects manage asynchronous actions without cluttering your components or services with these concerns.

2. Can NgRx Store be used without Effects?

Yes, it is possible to use NgRx Store without Effects. Store is responsible for managing the state, and you can dispatch actions, handle reducers, and select state without incorporating any effect handlers. However, Effects become essential when you need to deal with side effects like API calls, timers, and other asynchronous operations.

3. How do you set up NgRx Store and Effects in an Angular application?

Setting up NgRx Store and Effects involves several steps:

  1. Install NgRx Packages:

    npm install @ngrx/store @ngrx/effects @ngrx/store-devtools
    
  2. Create Store Module: Define actions, reducers, and effects.

    // actions.ts
    import { createAction, props } from '@ngrx/store';
    
    export const loadData = createAction('[Data Page] Load Data');
    export const loadDataSuccess = createAction(
      '[Data API] Data Loaded Success',
      props<{ data: any[] }>()
    );
    export const loadDataFailure = createAction(
      '[Data API] Data Loaded Failure',
      props<{ error: any }>()
    );
    
    // reducers.ts
    import { createReducer, on } from '@ngrx/store';
    import * as DataActions from './actions';
    
    export interface State {
      data: any[];
      error: string | null;
      status: 'pending' | 'loading' | 'error' | 'success';
    }
    
    export const initialState: State = {
      data: [],
      error: null,
      status: 'pending'
    };
    
    export const dataReducer = createReducer(
      initialState,
      on(DataActions.loadData, (state) => ({ ...state, status: 'loading' })),
      on(DataActions.loadDataSuccess, (state, { data }) => ({
        ...state,
        data: data,
        status: 'success'
      })),
      on(DataActions.loadDataFailure, (state, { error }) => ({
        ...state,
        error: error,
        status: 'error'
      }))
    );
    
  3. Register Store in AppModule:

    // app.module.ts
    import { NgModule } from '@angular/core';
    import { BrowserModule } from '@angular/platform-browser';
    import { StoreModule } from '@ngrx/store';
    import { EffectsModule } from '@ngrx/effects';
    import { DataEffects } from './data.effects';
    import { dataReducer } from './reducers';
    
    @NgModule({
      declarations: [
        // components
      ],
      imports: [
        BrowserModule,
        StoreModule.forRoot({ data: dataReducer }),
        EffectsModule.forRoot([DataEffects]),
      ],
      providers: [],
      bootstrap: [/* bootstrap components */]
    })
    export class AppModule {}
    
  4. Implement Effects: Create an Effects class and use createEffect.

    // data.effects.ts
    import { Injectable } from '@angular/core';
    import { Actions, createEffect, ofType } from '@ngrx/effects';
    import { DataActions } from './actions';
    import { exhaustMap, map, catchError } from 'rxjs/operators';
    import { DataService } from './data.service';
    import { of } from 'rxjs';
    
    @Injectable()
    export class DataEffects {
      loadData$ = createEffect(() =>
        this.actions$.pipe(
          ofType(DataActions.loadData),
          exhaustMap(() =>
            this.dataService.loadData().pipe(
              map(data => DataActions.loadDataSuccess({ data })),
              catchError(error => of(DataActions.loadDataFailure({ error })))
            )
          )
        )
      );
    
      constructor(private readonly actions$: Actions, private dataService: DataService) {}
    }
    

4. How do you dispatch actions to NgRx Store in Angular components?

To dispatch actions in Angular components, you need to inject the Store service and use the dispatch() method.

// data.component.ts
import { Component } from '@angular/core';
import { Store } from '@ngrx/store';
import { loadData } from './actions';

@Component({
  selector: 'app-data',
  template: `
    <button (click)="loadData()">Load Data</button>
  `,
})
export class DataComponent {
  constructor(private store: Store) {}

  loadData() {
    this.store.dispatch(loadData());
  }
}

5. How can you select data from the NgRx Store in Angular components?

Selecting data from the NgRx Store is done using the select() method from the Store service.

// data.component.ts
import { Component, OnInit } from '@angular/core';
import { Store } from '@ngrx/store';
import { selectData, selectError, selectStatus } from './selectors';

@Component({
  selector: 'app-data',
  template: `
    <p *ngIf="status === 'loading'">Loading...</p>
    <p *ngIf="status === 'error'">Error: {{ error }}</p>
    <div *ngIf="status === 'success'">
      <ul>
        <li *ngFor="let item of data">{{ item }}</li>
      </ul>
    </div>
  `,
})
export class DataComponent implements OnInit {
  data$ = this.store.select(selectData);
  error$ = this.store.select(selectError);
  status$ = this.store.select(selectStatus);

  constructor(private store: Store) {}

  ngOnInit() {}
}

Selectors can be defined as follows:

// selectors.ts
import { createFeatureSelector, createSelector } from '@ngrx/store';
import { State } from './reducers';

export const getDataState = createFeatureSelector<State>('data');

export const selectData = createSelector(getDataState, state => state.data);
export const selectError = createSelector(getDataState, state => state.error);
export const selectStatus = createSelector(getDataState, state => state.status);

6. What are the advantages of using NgRx Store and Effects?

Using NgRx Store and Effects offers several advantages:

  • Centralized State Management: All state is stored in a single, immutable store, making it easier to manage, debug, and test.
  • Predictability and Traceability: By defining actions and reducers, the state changes are predictable and traceable, which helps in understanding the flow of data in a complex application.
  • Separation of Concerns: Effects handle side effects, keeping your components and services clean and focused on their primary responsibilities.
  • Developer Tools: NgRx Store DevTools provide powerful debugging and monitoring capabilities, enhancing the development workflow.

7. How do NgRx Actions differ from regular functions or services in Angular?

NgRx Actions are special data structures (plain objects) that describe changes to the application's state. Key differences include:

  • Immutability: Actions are immutable, meaning their properties cannot be changed once the action is created, providing predictable state transitions.
  • Standardization: They follow a standard structure with a type property that clearly describes the action's purpose.
  • Dispatching: Actions are dispatched to the store, triggering state changes based on the defined reducers.
  • Testing: Easier to test since they are simple data objects and can be easily created and passed to reducers for unit testing.
  • Logging and Debugging: Since actions are centralized, they are easier to log and debug.

Regular functions or services, on the other hand, handle business logic, perform operations, and can modify states directly, which can lead to harder-to-track and manage state changes.

8. How do you handle loading states and errors with NgRx Effects?

Handling loading states and errors in NgRx Effects involves properly managing the state through actions. Here's an example:

  1. Define Actions:

    // actions.ts
    export const loadData = createAction('[Data Page] Load Data');
    export const loadDataSuccess = createAction('[Data API] Data Loaded Success', props<{ data: any[] }>());
    export const loadDataFailure = createAction('[Data API] Data Loaded Failure', props<{ error: any }>());
    
  2. Create Reducers:

    // reducers.ts
    import { on, createReducer } from '@ngrx/store';
    import * as DataActions from './actions';
    
    export interface State {
      data: any[];
      error: string | null;
      status: 'pending' | 'loading' | 'error' | 'success';
    }
    
    export const initialState: State = {
      data: [],
      error: null,
      status: 'pending'
    };
    
    export const dataReducer = createReducer(
      initialState,
      on(DataActions.loadData, state => ({ ...state, status: 'loading' })),
      on(DataActions.loadDataSuccess, (state, { data }) => ({ ...state, data, status: 'success' })),
      on(DataActions.loadDataFailure, (state, { error }) => ({ ...state, error, status: 'error' }))
    );
    
  3. Implement Effects: Handle success and error cases within the effect.

    // data.effects.ts
    import { Injectable } from '@angular/core';
    import { Actions, createEffect, ofType } from '@ngrx/effects';
    import { DataActions } from './actions';
    import { exhaustMap, map, catchError } from 'rxjs/operators';
    import { DataService } from './data.service';
    import { of } from 'rxjs';
    
    @Injectable()
    export class DataEffects {
      loadData$ = createEffect(() =>
        this.actions$.pipe(
          ofType(DataActions.loadData),
          exhaustMap(() =>
            this.dataService.loadData().pipe(
              map(data => DataActions.loadDataSuccess({ data })),
              catchError(error => of(DataActions.loadDataFailure({ error })))
            )
          )
        )
      );
    
      constructor(private readonly actions$: Actions, private dataService: DataService) {}
    }
    
  4. Select State in Components: Use selectors to bind the loading status and error messages to your component templates.

    // data.component.ts
    import { Component } from '@angular/core';
    import { Store } from '@ngrx/store';
    import { loadData, selectData, selectError, selectStatus } from './';
    
    @Component({
      selector: 'app-data',
      template: `
        <p *ngIf="status === 'loading'">Loading...</p>
        <p *ngIf="status === 'error'">Error: {{ error }}</p>
        <ul *ngIf="status === 'success'">
          <li *ngFor="let item of data">{{ item }}</li>
        </ul>
      `,
    })
    export class DataComponent {
      data$ = this.store.select(selectData);
      error$ = this.store.select(selectError);
      status$ = this.store.select(selectStatus);
    
      constructor(private store: Store) {}
    
      loadData() {
        this.store.dispatch(loadData());
      }
    }
    

9. Can NgRx Store and Effects work with other state management solutions like Redux Toolkit?

While NgRx and Redux Toolkit share a similar philosophy of state management based on actions, reducers, and immutable state, they are not directly compatible due to differences in implementation. However, you can learn from Redux Toolkit's design principles to improve your NgRx usage, such as using Immer for immutability and creating reusable slice-like reducers with NgRx Entity and Data.

10. What are some common mistakes to avoid when using NgRx Store and Effects?

Here are some common mistakes to avoid when using NgRx Store and Effects:

  • Mutating State: Never mutate the state directly. Always return a new state object.
  • Nested Selectors: Avoid deeply nested selectors; flattening them can improve performance.
  • Unnecessary Side Effects: Only use Effects for side effects; simple data transformations should be handled by selectors.
  • Misusing Actions: Actions should be purely descriptive of the intent or event. Avoid embedding too much business logic in actions.
  • Ignoring Error Handling: Always handle errors in Effects to prevent the app from crashing and provide meaningful feedback to users.

You May Like This Related .NET Topic

Login to post a comment.