1. Service-based State Management
Use Angular services to share state between components.
@Injectable({ providedIn: 'root' })
export class CounterService {
count = 0;
increment() { this.count++; }
decrement() { this.count--; }
}
Inject the service in components:
constructor(private counterService: CounterService) {}
increment() { this.counterService.increment(); }
- Simple and easy for small apps
- State persists as long as the service is alive
2. RxJS-based State
Use Subjects or BehaviorSubjects to create reactive shared state.
private countSubject = new BehaviorSubject<number>(0);
count$ = this.countSubject.asObservable();
increment() {
this.countSubject.next(this.countSubject.value + 1);
}
- Components subscribe to
count$ to get updates - Makes state reactive and easier to manage asynchronously
3. LocalStorage & SessionStorage
For persistent state across reloads, store data in LocalStorage or SessionStorage:
localStorage.setItem('user', JSON.stringify(user));
const storedUser = JSON.parse(localStorage.getItem('user') || '{}');
- LocalStorage persists until manually cleared
- SessionStorage persists only for the current tab/session
4. NgRx Basics
NgRx is a Redux-inspired state management library for Angular. It provides a single source of truth, making large apps predictable.
4.1 Actions
Define events that describe state changes:
import { createAction, props } from '@ngrx/store';
export const increment = createAction('[Counter] Increment');
export const decrement = createAction('[Counter] Decrement');
export const set = createAction('[Counter] Set', props<{ value: number }>());
4.2 Reducers
Reducers define how the state changes in response to actions:
import { createReducer, on } from '@ngrx/store';
import { increment, decrement, set } from './counter.actions';
export const initialState = 0;
export const counterReducer = createReducer(
initialState,
on(increment, state => state + 1),
on(decrement, state => state - 1),
on(set, (state, { value }) => value)
);
4.3 Effects
Effects handle side effects, like API calls, outside of the reducer:
import { Actions, createEffect, ofType } from '@ngrx/effects';
import { Injectable } from '@angular/core';
import { EMPTY } from 'rxjs';
import { map, mergeMap, catchError } from 'rxjs/operators';
import { ApiService } from '../api.service';
import * as CounterActions from './counter.actions';
@Injectable()
export class CounterEffects {
loadData$ = createEffect(() =>
this.actions$.pipe(
ofType(CounterActions.loadData),
mergeMap(() => this.api.getData()
.pipe(
map(data => CounterActions.loadDataSuccess({ data })),
catchError(() => EMPTY)
))
)
);
constructor(private actions$: Actions, private api: ApiService) {}
}
4.4 Store
The Store is a global state container for the app:
import { Store } from '@ngrx/store';
import { increment } from './counter.actions';
constructor(private store: Store<{ counter: number }>) {}
incrementCounter() {
this.store.dispatch(increment());
}
4.5 Selectors
Selectors retrieve data from the store:
import { createSelector } from '@ngrx/store';
export const selectCounter = (state: { counter: number }) => state.counter;
Usage in component:
counter$ = this.store.select(selectCounter);
Summary
- Service-based state is simple for small apps.
- RxJS-based state provides reactive updates between components.
- LocalStorage / SessionStorage allows persistent state across reloads.
- NgRx provides a structured, predictable state management solution for large-scale apps:
- Actions → define events
- Reducers → define state changes
- Effects → handle side effects
- Store → centralized state container
- Selectors → read state efficiently