State Management in Angular: NgRx vs NGXS
Dipak Ahirav
Posted on June 7, 2024
State management is a crucial aspect of modern web applications, ensuring a predictable and maintainable state across your application. In Angular, two popular state management libraries are NgRx and NGXS. In this blog post, we'll explore both libraries in detail, providing examples to help you understand how to implement them in your Angular applications.
please subscribe to my YouTube channel to support my channel and get more web development tutorials.
NgRx
NgRx is a state management library for Angular inspired by Redux. It provides a robust framework for managing application state in a predictable and scalable way.
Key Features of NgRx:
- Based on Redux Pattern: NgRx is heavily inspired by Redux, which is a predictable state container for JavaScript apps. It follows the principles of Redux very closely.
- Action-Reducer-Effect Pattern: NgRx uses actions, reducers, and effects to manage state. Actions are dispatched to describe state changes, reducers handle these actions to modify the state, and effects handle side effects like HTTP requests.
- Boilerplate Code: NgRx requires more boilerplate code compared to NGXS. Developers need to create actions, reducers, and effects explicitly.
- Immutability: NgRx enforces immutability strictly. State changes result in new state objects, ensuring the state is not mutated directly.
- Ecosystem: NgRx has a robust ecosystem with tools like NgRx Store, NgRx Effects, NgRx Entity, and NgRx Router Store, providing a comprehensive suite for state management.
Setting Up NgRx
1.Install NgRx:
To get started with NgRx, install the necessary packages:
ng add @ngrx/store
ng add @ngrx/effects
ng add @ngrx/store-devtools
2.Define State:
Create an interface to define the shape of the state.
// src/app/state/counter.state.ts
export interface CounterState {
count: number;
}
export const initialState: CounterState = {
count: 0
};
3.Define Actions:
Create actions to describe changes to the state.
// src/app/state/counter.actions.ts
import { createAction } from '@ngrx/store';
export const increment = createAction('[Counter] Increment');
export const decrement = createAction('[Counter] Decrement');
export const reset = createAction('[Counter] Reset');
4.Define Reducers:
Create a reducer to handle actions and update the state.
// src/app/state/counter.reducer.ts
import { createReducer, on } from '@ngrx/store';
import { increment, decrement, reset } from './counter.actions';
import { CounterState, initialState } from './counter.state';
export const counterReducer = createReducer(
initialState,
on(increment, state => ({ ...state, count: state.count + 1 })),
on(decrement, state => ({ ...state, count: state.count - 1 })),
on(reset, state => ({ ...state, count: 0 }))
);
5.Register Reducer:
Register the reducer in your AppModule
.
// src/app/app.module.ts
import { NgModule } from '@angular/core';
import { BrowserModule } from '@angular/platform-browser';
import { StoreModule } from '@ngrx/store';
import { counterReducer } from './state/counter.reducer';
@NgModule({
declarations: [AppComponent],
imports: [
BrowserModule,
StoreModule.forRoot({ counter: counterReducer })
],
providers: [],
bootstrap: [AppComponent]
})
export class AppModule { }
6.Use State in Components:
Dispatch actions and select state values in your components.
// src/app/counter/counter.component.ts
import { Component } from '@angular/core';
import { Store } from '@ngrx/store';
import { increment, decrement, reset } from '../state/counter.actions';
import { CounterState } from '../state/counter.state';
@Component({
selector: 'app-counter',
template: `
<div>
<button (click)="increment()">Increment</button>
<button (click)="decrement()">Decrement</button>
<button (click)="reset()">Reset</button>
<div>Count: {{ count$ | async }}</div>
</div>
`
})
export class CounterComponent {
count$ = this.store.select(state => state.counter.count);
constructor(private store: Store<{ counter: CounterState }>) {}
increment() {
this.store.dispatch(increment());
}
decrement() {
this.store.dispatch(decrement());
}
reset() {
this.store.dispatch(reset());
}
}
NGXS
NGXS is a state management library for Angular that aims to be simple and intuitive. It uses decorators to define state, actions, and selectors, providing a more concise syntax.
Key Features of NGXS:
- Inspired by Redux: While NGXS is inspired by Redux, it aims to be simpler and more intuitive than NgRx.
- State-Action Pattern: NGXS uses a state-action pattern where state is organized into classes. Actions are methods on these state classes, reducing the need for separate action and reducer files.
- Less Boilerplate: NGXS requires less boilerplate code, making it quicker and easier to set up and manage state. State, actions, and selectors are often defined in a single file.
- Mutability: NGXS allows direct mutation of state within actions, which can be more intuitive for developers but may lead to less predictable state changes.
- Decorators: NGXS leverages decorators to define state, actions, and selectors, providing a more declarative and readable approach.
- Simplicity: NGXS is designed to be simpler and more accessible, making it a good choice for smaller projects or developers looking for a more straightforward state management solution.
Setting Up NGXS
1.Install NGXS:
To get started with NGXS, install the necessary package:
ng add @ngxs/store
2.Define State:
Create a state model and state class with actions.
// src/app/state/counter.state.ts
import { State, Action, StateContext, Selector } from '@ngxs/store';
export class Increment {
static readonly type = '[Counter] Increment';
}
export class Decrement {
static readonly type = '[Counter] Decrement';
}
export class Reset {
static readonly type = '[Counter] Reset';
}
export interface CounterStateModel {
count: number;
}
@State<CounterStateModel>({
name: 'counter',
defaults: {
count: 0
}
})
export class CounterState {
@Selector()
static getCount(state: CounterStateModel) {
return state.count;
}
@Action(Increment)
increment(ctx: StateContext<CounterStateModel>) {
const state = ctx.getState();
ctx.setState({ count: state.count + 1 });
}
@Action(Decrement)
decrement(ctx: StateContext<CounterStateModel>) {
const state = ctx.getState();
ctx.setState({ count: state.count - 1 });
}
@Action(Reset)
reset(ctx: StateContext<CounterStateModel>) {
ctx.setState({ count: 0 });
}
}
3.Register State:
Register the state class in your AppModule
.
// src/app/app.module.ts
import { NgModule } from '@angular/core';
import { BrowserModule } from '@angular/platform-browser';
import { NgxsModule } from '@ngxs/store';
import { CounterState } from './state/counter.state';
@NgModule({
declarations: [AppComponent],
imports: [
BrowserModule,
NgxsModule.forRoot([CounterState])
],
providers: [],
bootstrap: [AppComponent]
})
export class AppModule { }
4.Use State in Components:
Dispatch actions and select state values in your components.
// src/app/counter/counter.component.ts
import { Component } from '@angular/core';
import { Store, Select } from '@ngxs/store';
import { Observable } from 'rxjs';
import { Increment, Decrement, Reset, CounterState } from '../state/counter.state';
@Component({
selector: 'app-counter',
template: `
<div>
<button (click)="increment()">Increment</button>
<button (click)="decrement()">Decrement</button>
<button (click)="reset()">Reset</button>
<div>Count: {{ count$ | async }}</div>
</div>
`
})
export class CounterComponent {
@Select(CounterState.getCount) count$: Observable<number>;
constructor(private store: Store) {}
increment() {
this.store.dispatch(new Increment());
}
decrement() {
this.store.dispatch(new Decrement());
}
reset() {
this.store.dispatch(new Reset());
}
}
Summary
Both NgRx and NGXS offer robust state management solutions for Angular
applications, but they have different approaches:
-
NgRx:
- Based on Redux Pattern: Closely follows Redux principles.
- Action-Reducer-Effect Pattern: Uses actions, reducers, and effects.
- Boilerplate Code: Requires more boilerplate code.
- Immutability: Strictly enforces immutability.
- Ecosystem: Comprehensive suite of tools (NgRx Store, NgRx Effects, etc.).
-
NGXS:
- Inspired by Redux: Simpler and more intuitive.
- State-Action Pattern: Organizes state into classes with methods.
- Less Boilerplate: Requires less boilerplate code.
- Mutability: Allows direct state mutation.
- Decorators: Uses decorators for state, actions, and selectors.
- Simplicity: Designed for simpler and more accessible state management.
NgRx is best suited for larger, more complex applications where predictability and immutability are crucial. It follows a strict Redux pattern and requires more boilerplate code. NGXS, on the other hand, is ideal for simpler or mid-sized applications, offering a more straightforward setup with less boilerplate and a more intuitive approach to state management.
By following the examples provided, you can start implementing state management in your Angular applications using either NgRx or NGXS, making your application more predictable and easier to maintain.
Follow me for more tutorials and tips on web development. Feel free to leave comments or questions below!
Follow and Subscribe:
- Website: Dipak Ahirav
- Email: dipaksahirav@gmail.com
- Instagram: devdivewithdipak
- YouTube: devDive with Dipak
- LinkedIn: Dipak Ahirav
Posted on June 7, 2024
Join Our Newsletter. No Spam, Only the good stuff.
Sign up to receive the latest update from our blog.