We use cookies to enhance your experience on the site
CodeWorlds

State Management - The Shogun's Treasury

Managing state is like running the shogun's treasury - it requires order, control, and precise accounting of every resource. In feudal Japan, the shogun had to know the state of his resources at all times.

What is Application State?

State is all the data that an application stores:

  • UI State - whether a modal is open, the active tab
  • Server State - data from API (users, products)
  • Form State - form values and validation
  • Router State - current URL path

Simple State Management with Signals

1import { Injectable, signal, computed, inject } from '@angular/core';
2import { HttpClient } from '@angular/common/http';
3
4interface SamuraiState {
5  samurai: Samurai[];
6  selectedId: number | null;
7  loading: boolean;
8  error: string | null;
9}
10
11@Injectable({ providedIn: 'root' })
12export class SamuraiStore {
13  private http = inject(HttpClient);
14
15  // Private state signals
16  private state = signal<SamuraiState>({
17    samurai: [],
18    selectedId: null,
19    loading: false,
20    error: null
21  });
22
23  // Public selectors (readonly)
24  readonly samurai = computed(() => this.state().samurai);
25  readonly selectedId = computed(() => this.state().selectedId);
26  readonly loading = computed(() => this.state().loading);
27  readonly error = computed(() => this.state().error);
28
29  // Derived selectors
30  readonly selectedSamurai = computed(() => {
31    const id = this.selectedId();
32    return id ? this.samurai().find(s => s.id === id) : null;
33  });
34
35  readonly samuraiCount = computed(() => this.samurai().length);
36
37  readonly samuraiByClan = computed(() => {
38    const grouped = new Map<string, Samurai[]>();
39    for (const s of this.samurai()) {
40      const list = grouped.get(s.clan) || [];
41      list.push(s);
42      grouped.set(s.clan, list);
43    }
44    return grouped;
45  });
46
47  // Actions (state mutations)
48  loadSamurai(): void {
49    this.state.update(s => ({ ...s, loading: true, error: null }));
50
51    this.http.get<Samurai[]>('/api/samurai').subscribe({
52      next: (samurai) => {
53        this.state.update(s => ({ ...s, samurai, loading: false }));
54      },
55      error: (err) => {
56        this.state.update(s => ({
57          ...s,
58          loading: false,
59          error: err.message
60        }));
61      }
62    });
63  }
64
65  selectSamurai(id: number | null): void {
66    this.state.update(s => ({ ...s, selectedId: id }));
67  }
68
69  addSamurai(samurai: Samurai): void {
70    this.state.update(s => ({
71      ...s,
72      samurai: [...s.samurai, samurai]
73    }));
74  }
75
76  removeSamurai(id: number): void {
77    this.state.update(s => ({
78      ...s,
79      samurai: s.samurai.filter(sam => sam.id !== id)
80    }));
81  }
82}

The shogun's treasury works only when it has one way in and one way out - keep the state in a private signal, expose nothing to the outside but

computed
selectors, and let every change pass through an action that builds a new state object rather than quietly overwriting the old one.

Go to CodeWorlds