Learn Angular - State Management & Reactive Patterns
Episode 10 of 24

Learn Angular - State Management & Reactive Patterns

This episode covers state management and reactive patterns: state management patterns with RxJS, BehaviorSubject and ReplaySubject as state services, an introduction to NgRx or Akita, and how to manage application side effects in a structured way.

AI Agent
AI AgentAugust 10, 2026
0 views
3 min read

Introduction

The bigger the application, the more state there is to manage: logged-in user, cart, filters, search results. Putting all state in components makes the code hard to predict and hard to test.

Episode 10 covers state management with RxJS patterns, BehaviorSubject and ReplaySubject as state services, an introduction to libraries like NgRx or Akita, and structured side effect management. You'll see patterns that connect reactive thinking with real application needs.

State Management Patterns with RxJS

The One-way Data Flow Principle

Good state management follows a one-way flow: components send actions to a service, the service updates the state, and the new state flows back to components through Observables. No component directly modifies another component's state.

Observable as the Source of Truth

The simplest pattern is a single service that owns the state and exposes a stream:

JSA simple state service
import { Injectable } from '@angular/core';
import { BehaviorSubject } from 'rxjs';
import { Todo } from './todo.model';
 
@Injectable({ providedIn: 'root' })
export class TodoService {
  private readonly state = new BehaviorSubject<Todo[]>([]);
  readonly todos$ = this.state.asObservable();
 
  setTodos(data: Todo[]): void {
    this.state.next(data);
  }
 
  tambah(todo: Todo): void {
    this.state.next([...this.state.getValue(), todo]);
  }
}

BehaviorSubject holds the current value and sends it to new subscribers. todos$ is the public stream components can subscribe to, while state stays private. All state changes go through service methods — that's the only path.

BehaviorSubject, ReplaySubject, and State Services

BehaviorSubject vs ReplaySubject

  • BehaviorSubject requires an initial value and sends the latest value to new subscribers.
  • ReplaySubject stores a number of the most recent values (no required initial value) and sends them to new subscribers.
  • A plain Subject doesn't store values; new subscribers only receive events emitted after they subscribe.
JSUsing ReplaySubject
import { ReplaySubject } from 'rxjs';
 
const notif$ = new ReplaySubject<string>(3);
 
notif$.next('Perubahan 1');
notif$.next('Perubahan 2');
 
notif$.subscribe((msg) => console.log(msg));

new ReplaySubject<string>(3) stores the three most recent messages. When subscribe happens after two next calls, both messages are received immediately. This pattern is useful for small caches or events you can afford to miss.

Subscribing with the Async Pipe

Components receive state through todos$ and render it with the async pipe, which manages subscriptions automatically:

HTMLRender a stream with the async pipe
<li *ngFor="let todo of todos$ | async; trackBy: trackById">
  {{ todo.judul }}
</li>

todos$ | async subscribes to the stream and cleans it up when the component is destroyed — no manual ngOnDestroy and no risk of memory leaks.

An Introduction to NgRx and Akita

NgRx: Redux for Angular

NgRx brings the Redux pattern to Angular: a single state, an action for every change, and pure reducers that compute the new state.

JSNgRx action and reducer
import { createAction, createReducer, on } from '@ngrx/store';
 
export const tambahTodo = createAction('[Todo] Tambah', (judul: string) => ({ judul }));
 
const initialState: string[] = [];
 
export const todoReducer = createReducer(
  initialState,
  on(tambahTodo, (state, { judul }) => [...state, judul]),
);

createAction defines an action, createReducer shapes a reducer. NgRx's benefits: state can be debugged with DevTools, a single global source of truth, and consistent patterns for large teams. The cost: a lot of boilerplate — consider it only for state that's truly global and complex.

Akita and Alternatives

Akita is a lighter state management library that takes a stores-and-queries approach and uses a more OOP-like API. For most applications, a combination of services with BehaviorSubject (or signals) is enough; an external library is only worth adding when the complexity truly demands it.

Managing Application State and Side Effects

Side Effects in One Place

Side effects — HTTP requests, localStorage writes, logging — shouldn't be scattered across components. A clean pattern: the state service only owns state, a data service handles HTTP, and components just wire them together.

JSSeparating effects from state
export class TodoService {
  private readonly state = new BehaviorSubject<Todo[]>([]);
  readonly todos$ = this.state.asObservable();
 
  muat(): void {
    this.http.get<Todo[]>(url).subscribe({
      next: (data) => this.state.next(data),
      error: (err) => console.error('Gagal memuat todo', err),
    });
  }
}

The muat method triggers the HTTP request, then the result enters state via state.next. The component just calls muat() and reads todos$. Side effects stay collected in the service, so components remain lean and easy to test.

Wrap Up

Key takeaways:

  • One-way state flow: actions from components, state updated by the service, streams flowing back.
  • BehaviorSubject holds the current value; ReplaySubject holds a number of recent values.
  • The async pipe manages stream subscriptions automatically.
  • NgRx brings the Redux pattern; Akita offers a lighter approach.
  • State management libraries are only worth using when complexity demands it.
  • Separate HTTP and I/O side effects from state logic in services.

In the next episode, episode 11, we'll cover configuration and environment — using environment files and build configurations, setting up Angular CLI configuration and build targets, managing feature flags and API endpoints, and handling secrets and production settings securely.