Managing State in Angular with ngrx/store

Sumit kumar Singh

--

Managing the state in Angular applications is a crucial aspect of building scalable, maintainable, and efficient software. As applications grow in complexity, handling state management becomes increasingly challenging. One popular solution in the Angular ecosystem is ngrx/store, which is inspired by Redux, a predictable state container for JavaScript applications. In this comprehensive guide, we will delve deep into the concepts, implementation, and best practices of managing state in Angular using ngrx/store.

Understanding the Need for State Management

Before diving into the specifics of ngrx/store, it’s essential to understand why state management is vital in modern web applications. In a typical Angular application, state can be defined as any data that should be saved and persisted across component lifecycles. This includes user data, application configuration, and various UI states.

Managing this state is critical for several reasons:

  1. Predictability and Debugging: A predictable state container ensures that the application’s behaviour is easy to understand and debug. With a single source of truth, developers can trace bugs and unexpected behaviours efficiently.
  2. Maintainability: A well-structured state management system makes it easier to maintain and scale the application. As the application grows, managing state in a consistent manner becomes crucial to prevent spaghetti code.
  3. Performance Optimization: Efficient state management can lead to performance improvements. By managing state changes intelligently, unnecessary rendering and computations can be avoided, resulting in a smoother user experience.

Introduction to ngrx/store

ngrx/store is a state management library for Angular applications. It implements the Redux pattern, providing a predictable state container that can be accessed across components. At its core, ngrx/store revolves around the following concepts:

  1. Store: The central store holds the application state. It is a single, immutable data structure that represents the entire state of the application.
  2. Actions: Actions are plain JavaScript objects that describe state changes. They are dispatched to the store and trigger reducers.
  3. Reducers: Reducers are pure functions that specify how the application’s state changes in response to actions. They take the current state and an action as input and return a new state.
  4. Selectors: Selectors are pure functions used to extract specific pieces of data from the store. They encapsulate the logic for deriving computed state values.

Implementing ngrx/store in Angular

Setting Up ngrx/store

To use ngrx/store in your Angular application, you first need to install the required packages:

ng add @ngrx/store

This command installs the necessary packages and sets up the basic folder structure for your state management.

Defining Actions

Actions represent events or user interactions in the application. They are defined as classes or functions that return action objects. For example, consider an action to add a new item:

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

export const addItem = createAction('[Item] Add Item', props<{ item: string }>());

Here, createAction is a utility function from ngrx/store that creates an action with a type and optional payload.

Creating Reducers

Reducers define how the application state changes in response to actions. They are pure functions that take the current state and an action as arguments and return a new state. Reducers are combined to form the application’s root reducer.

import { createReducer, on } from '@ngrx/store';
import { addItem } from './item.actions';

export interface AppState {
items: string[];
}

export const initialState: AppState = {
items: []
};

export const itemReducer = createReducer(
initialState,
on(addItem, (state, { item }) => {
return { ...state, items: [...state.items, item] };
})
);

In this example, the itemReducer handles the addItem action, updating the state to include the new item in the items array.

Setting Up the Store

To create the store in your Angular application, you provide the reducers at the root level of your module:

import { StoreModule } from '@ngrx/store';
import { itemReducer } from './item.reducer';

@NgModule({
imports: [
// other modules
StoreModule.forRoot({ items: itemReducer })
]
})
export class AppModule { }

Here, itemReducer is combined with other reducers (if any) using the forRoot method from @ngrx/store.

Dispatching Actions

Actions are dispatched from components or services to trigger state changes. You can inject the Store service into your components and dispatch actions using the dispatch method:

import { Component } from '@angular/core';
import { Store } from '@ngrx/store';
import { addItem } from './item.actions';

@Component({
selector: 'app-item-list',
template: `
<ul>
<li *ngFor="let item of items$ | async">{{ item }}</li>
</ul>
<button (click)="addItem('New Item')">Add Item</button>
`
})
export class ItemListComponent {
items$ = this.store.select(state => state.items);

constructor(private store: Store) {}

addItem(item: string) {
this.store.dispatch(addItem({ item }));
}
}

In this example, the addItem action is dispatched when the "Add Item" button is clicked, updating the application state.

Creating Selectors

Selectors are used to extract specific pieces of data from the store. They encapsulate the logic for computing derived state values. Selectors are created using the createSelector function from @ngrx/store:

import { createSelector, createFeatureSelector } from '@ngrx/store';
import { AppState } from './item.reducer';

export const selectItemState = createFeatureSelector<AppState>('items');

export const selectItems = createSelector(
selectItemState,
state => state.items
);

Here, selectItemState is a feature selector that selects the items property from the root state. selectItems is a selector that uses selectItemState to extract the items array from the store.

Subscribing to State Changes

Components can subscribe to state changes using selectors. By utilizing the async pipe, Angular takes care of subscribing and unsubscribing from the observable automatically:

import { Component } from '@angular/core';
import { Store } from '@ngrx/store';
import { selectItems } from './selectors';

@Component({
selector: 'app-item-list',
template: `
<ul>
<li *ngFor="let item of items$ | async">{{ item }}</li>
</ul>
`
})
export class ItemListComponent {
items$ = this.store.select(selectItems);

constructor(private store: Store) {}
}

Here, items$ is an observable that emits the items array from the store. The async pipe subscribes to this observable, automatically updating the UI when the state changes.

Best Practices and Advanced Techniques

Immutability and Pure Functions

Reducers and selectors should be pure functions. They should not modify the input state but instead create new objects or arrays. Immutability ensures predictability and helps prevent unintended side effects.

Async Operations

For handling asynchronous operations, ngrx/effects can be used. Effects are used to interact with services, perform HTTP requests, and dispatch new actions based on the results. This ensures that side effects are isolated and handled separately from the main application state.

Optimizing Performance

To optimize performance, use the trackBy function in Angular's ngFor directive. This function allows you to specify a unique identifier for each item in the list, preventing unnecessary re-rendering of unchanged items.

trackByFn(index: number, item: any): number {
return item.id; // Use a unique identifier for the items
}

Selective Component Rendering

By using selectors effectively, components can subscribe only to the specific parts of the state they need. This selective rendering approach ensures that components re-render only when relevant parts of the state change, improving performance.

Testing

ngrx/store provides testing utilities that allow you to test actions, reducers, selectors, and effects. Writing unit tests for your state management logic ensures its correctness and reliability.

Conclusion

Effective state management is essential for building robust and scalable Angular applications. ngrx/store provides a powerful solution by implementing the Redux pattern in an Angular context. By understanding the core concepts of actions, reducers, selectors, and the store, developers can create maintainable, predictable, and efficient applications. Additionally, embracing best practices and advanced techniques such as immutability, async operations, and performance optimization further enhances the quality and performance of Angular applications.

Implementing state management with ngrx/store might initially seem complex, but with practice and a deep understanding of the concepts, developers can harness their full potential to build high-quality Angular applications that meet the demands of modern web development.

This guide has provided an in-depth exploration of ngrx/store and its various aspects. By applying the knowledge gained here, developers can confidently tackle state management challenges in Angular applications, ensuring their projects are scalable, maintainable, and performant.

Thanks for reading!

I hope you found this article useful. If you have any questions or suggestions, please leave comments. Your feedback helps me to become better.

Don’t forget to subscribe⭐️

Facebook Page: https://www.facebook.com/designTechWorld1

Instagram Page: https://www.instagram.com/techd.esign/

Youtube Channel: https://www.youtube.com/@tech..Design/

Twitter: https://twitter.com/sumit_singh2311

Gear used:

Laptop: https://amzn.to/3yKkzaC

You can prefer React Book: https://amzn.to/3Tw29nx

Some extra books related to programming language:

https://amzn.to/3z3tW5s

https://amzn.to/40n4m6O

https://amzn.to/3Jzstse

https://amzn.to/3nbl8aE

*Important Disclaimer — “Amazon and the Amazon logo are trademarks of Amazon.com, Inc. or its affiliates.”

--

--

Sumit kumar Singh

YouTube: https://www.youtube.com/@tech..Design/ 📚 HTML,Angular, React,and JavaScript 🧑‍💻 Tips & tricks on Web Developing 👉 FULL STACK DEVELOPER