What is State Management in Angular?
State management is a way to manage the data (state) of your app so that it stays consistent and easy to use across all parts of the app. In Angular, state includes:
Data shown in the UI (like user profiles or product lists).
User interactions (like form inputs or button clicks).
App state (like whether the user is logged in).
Why Do We Need State Management?
Imagine a shopping cart app:
A user adds items on the product page.
The cart page should show those items.
The checkout page also needs to know what’s in the cart.
Instead of passing the same data between components manually, state management stores the data in one central place, making it accessible everywhere.
State Management in Angular 12
Angular provides ways to manage state effectively. The two main options are:
Using a Service (Simpler Method)
Using NgRx (Advanced Method)
1. State Management Using a Service
This is the easiest way to manage state.
Steps to Implement:
Create a Service to store and manage your app's state.
Use Subjects or BehaviorSubjects to update and share data across components.
Example: Counter App
Let’s build a simple counter app.
1. Create a Service
typescriptCopy codeimport { Injectable } from '@angular/core';
import { BehaviorSubject } from 'rxjs';
@Injectable({
providedIn: 'root',
})
export class CounterService {
private counter = new BehaviorSubject<number>(0); // Initial state
counter$ = this.counter.asObservable(); // Observable for components to subscribe
increment() {
this.counter.next(this.counter.value + 1); // Update state
}
decrement() {
this.counter.next(this.counter.value - 1);
}
}
2. Use the Service in Components
Counter Display Component
typescriptCopy codeimport { Component, OnInit } from '@angular/core';
import { CounterService } from './counter.service';
@Component({
selector: 'app-counter-display',
template: `<h1>Counter: {{ counter }}</h1>`,
})
export class CounterDisplayComponent implements OnInit {
counter = 0;
constructor(private counterService: CounterService) {}
ngOnInit() {
this.counterService.counter$.subscribe((value) => {
this.counter = value; // Update the UI when state changes
});
}
}
Counter Buttons Component
typescriptCopy codeimport { Component } from '@angular/core';
import { CounterService } from './counter.service';
@Component({
selector: 'app-counter-buttons',
template: `
<button (click)="increment()">+</button>
<button (click)="decrement()">-</button>
`,
})
export class CounterButtonsComponent {
constructor(private counterService: CounterService) {}
increment() {
this.counterService.increment();
}
decrement() {
this.counterService.decrement();
}
}
2. State Management Using NgRx
NgRx is a library for managing state in Angular apps, inspired by Redux. It’s useful for larger apps.
How NgRx Works:
Store: Holds the app’s state.
Actions: Describe what happened (e.g., "Add Item to Cart").
Reducers: Decide how the state changes based on actions.
Selectors: Get specific parts of the state.
Example: Shopping Cart App
1. Install NgRx
bashCopy codenpm install @ngrx/store
2. Create Actions
typescriptCopy codeimport { createAction, props } from '@ngrx/store';
export const addItem = createAction('[Cart] Add Item', props<{ item: string }>());
export const removeItem = createAction('[Cart] Remove Item', props<{ item: string }>());
3. Create a Reducer
typescriptCopy codeimport { createReducer, on } from '@ngrx/store';
import { addItem, removeItem } from './cart.actions';
export const initialState: string[] = [];
const _cartReducer = createReducer(
initialState,
on(addItem, (state, { item }) => [...state, item]),
on(removeItem, (state, { item }) => state.filter((i) => i !== item))
);
export function cartReducer(state: any, action: any) {
return _cartReducer(state, action);
}
4. Register the Store
typescriptCopy codeimport { NgModule } from '@angular/core';
import { StoreModule } from '@ngrx/store';
import { cartReducer } from './cart.reducer';
@NgModule({
imports: [
StoreModule.forRoot({ cart: cartReducer }),
],
})
export class AppModule {}
5. Use State in Components
Adding an Item
typescriptCopy codeimport { Component } from '@angular/core';
import { Store } from '@ngrx/store';
import { addItem } from './cart.actions';
@Component({
selector: 'app-add-item',
template: `<button (click)="add()">Add Item</button>`,
})
export class AddItemComponent {
constructor(private store: Store) {}
add() {
this.store.dispatch(addItem({ item: 'Apple' }));
}
}
Displaying the Cart
typescriptCopy codeimport { Component } from '@angular/core';
import { Store } from '@ngrx/store';
@Component({
selector: 'app-cart',
template: `<ul><li *ngFor="let item of cart$ | async">{{ item }}</li></ul>`,
})
export class CartComponent {
cart$ = this.store.select('cart');
constructor(private store: Store) {}
}
Which One Should You Use?
Services: Best for simple or medium-sized apps.
NgRx: Ideal for large, complex apps with lots of state to manage.