# State Management

Course: [Angular](https://www.devtutorials.ai/learn/angular) · Section: Application State
Source: https://www.devtutorials.ai/learn/angular/state/state-management

> Where to store app data: component state, shared state in services, BehaviorSubject and signal-based state, common patterns, and an overview of NgRx store, actions, reducers and selectors.

## State Management Fundamentals

**State** is the data that your app stores at a moment in time. Examples: the items in the cart, the signed-in user, the selected tab and the text in a search box.

**State management** means deciding **where** each piece of state is stored and **how** it is changed. Good state management keeps the screen correct: every component shows the same, current data.

### Real-World Example

In an online store, the header badge, the product page and the checkout page all need the cart. The cart is stored in one `CartStore` service, and every component reads it from there.

Components change the cart only with the service's methods, such as `add()`. So the badge and the checkout can never show different numbers.

<figure className="ngc-fig">
<svg viewBox="0 0 640 224" role="img" aria-label="Three components connect to one CartStore service: the header badge and checkout read from it, and the product page calls add().">
<defs><marker id="ngc-n2" viewBox="0 0 10 10" refX="9" refY="5" markerWidth="7" markerHeight="7" orient="auto-start-reverse"><path d="M0 0L10 5L0 10z" className="ngc-arrowhead" /></marker></defs><g className="ngc-step ngc-t1"><g className="ngc-node ngc-solid c-purple"><rect x="220" y="10" width="200" height="56" rx="9" /><text x="320.0" y="35" textAnchor="middle" style={{fontSize:"14px"}}>CartStore service</text><text className="ngc-sub" x="320.0" y="53" textAnchor="middle">items, add(), total</text></g></g><g className="ngc-step ngc-t2"><g className="ngc-node ngc-soft c-blue"><rect x="20" y="126" width="180" height="56" rx="9" /><text x="110.0" y="151" textAnchor="middle" style={{fontSize:"14px"}}>Header badge</text><text className="ngc-sub" x="110.0" y="169" textAnchor="middle">reads count</text></g></g><line className="ngc-link ngc-flow c-slate" x1="110" y1="124" x2="320" y2="68" markerEnd="url(#ngc-n2)" /><g className="ngc-step ngc-t3"><g className="ngc-node ngc-soft c-orange"><rect x="230" y="126" width="180" height="56" rx="9" /><text x="320.0" y="151" textAnchor="middle" style={{fontSize:"14px"}}>Product page</text><text className="ngc-sub" x="320.0" y="169" textAnchor="middle">calls add()</text></g></g><line className="ngc-link ngc-flow c-slate" x1="320" y1="124" x2="320" y2="68" markerEnd="url(#ngc-n2)" /><g className="ngc-step ngc-t4"><g className="ngc-node ngc-soft c-green"><rect x="440" y="126" width="180" height="56" rx="9" /><text x="530.0" y="151" textAnchor="middle" style={{fontSize:"14px"}}>Checkout</text><text className="ngc-sub" x="530.0" y="169" textAnchor="middle">reads total</text></g></g><line className="ngc-link ngc-flow c-slate" x1="530" y1="124" x2="320" y2="68" markerEnd="url(#ngc-n2)" /><text className="ngc-note" x="320" y="212" textAnchor="middle">One place stores the cart. Every component reads it from there.</text>
</svg>
<figcaption>Shared state lives in one service.</figcaption>
</figure>

There are two kinds of state:

| Kind | Who uses it | Example | Where to store it |
|---|---|---|---|
| Component state | One component | Is this panel open? | In the component |
| Shared state | Many components | Cart items, signed-in user | In a service |

## Component State

**Component state** is data that only one component needs. Store it in a signal in that component.

Each copy of the component has its own state. In the next example, each FAQ item opens and closes on its own.

```angular runnable title="Component state: each item has its own open flag"
import { Component, Input, signal } from '@angular/core';

@Component({
  selector: 'app-faq-item',
  template: `
    <p>
      <button (click)="open.update(v => !v)">{{ open() ? '−' : '+' }}</button>
      {{ question }}
    </p>
    @if (open()) {
      <p>{{ answer }}</p>
    }
  `,
})
export class FaqItem {
  @Input() question = '';
  @Input() answer = '';
  open = signal(false);
}

@Component({
  selector: 'app-root',
  imports: [FaqItem],
  template: `
    <app-faq-item question="How long is delivery?" answer="3 to 5 working days." />
    <app-faq-item question="Can I return an item?" answer="Yes, within 30 days." />
  `,
})
export class App {}
```

`open` is component state. No other component needs to know if this item is open.

## Shared State

**Shared state** is data that many components need. If each component stores its own copy, the copies can show different values.

You learned two ways to share data between components:

- **`@Input()` and `@Output()`:** good for a parent and its direct child. With many levels, you must pass the data through every level.
- **A service:** any component can inject it, at any level. This is the usual way to store shared state.

The next example shows the problem with copies. `Header` and `Sidebar` each store their own `cartCount`. The **Add** button changes only the copy in `Header`.

```angular runnable title="The problem: two copies of shared data"
import { Component, signal } from '@angular/core';

@Component({
  selector: 'app-header',
  template: `
    <p>Header cart: {{ cartCount() }}
      <button (click)="cartCount.update(n => n + 1)">Add</button></p>
  `,
})
export class Header {
  cartCount = signal(0);
}

@Component({
  selector: 'app-sidebar',
  template: `<p>Sidebar cart: {{ cartCount() }}</p>`,
})
export class Sidebar {
  cartCount = signal(0);
}

@Component({
  selector: 'app-root',
  imports: [Header, Sidebar],
  template: `<app-header /><app-sidebar />`,
})
export class App {}
```

Click **Add**. The header shows 1, but the sidebar still shows 0. The section "Signal-Based State" below fixes this with one shared service.

Keep only one copy of each piece of shared data. When the same data is stored in two places, the two copies soon show different values.

## Service-Based State

In **service-based state**, a service stores the shared data, and components read and change it through the service. You did this with the cart service in the Services lesson.

A good state service has three parts:

- **The state:** the data, kept private, so only the service can change it.
- **Read access:** fields or methods that components read.
- **Methods that change the state**, such as `add()` and `clear()`.

The next two sections show two ways to store the state inside the service: a `BehaviorSubject` and a signal.

## BehaviorSubject State

You learned `BehaviorSubject` in the RxJS lesson. It stores the current value and sends every new value to its subscribers. So it can store shared state.

- Keep the `BehaviorSubject` **private**.
- Give components an Observable with `asObservable()`. Components can read it, but cannot call `next()`.
- In the template, display it with the `async` pipe.

```angular runnable title="State in a BehaviorSubject"
import { Component, Injectable, inject } from '@angular/core';
import { AsyncPipe } from '@angular/common';
import { BehaviorSubject } from 'rxjs';

@Injectable({ providedIn: 'root' })
export class UserStore {
  private readonly user$ = new BehaviorSubject<string>('Guest');
  readonly user = this.user$.asObservable();

  login(name: string) {
    this.user$.next(name);
  }

  logout() {
    this.user$.next('Guest');
  }
}

@Component({
  selector: 'app-header',
  imports: [AsyncPipe],
  template: `<p>Header: Hello, {{ users.user | async }}</p>`,
})
export class Header {
  users = inject(UserStore);
}

@Component({
  selector: 'app-root',
  imports: [AsyncPipe, Header],
  template: `
    <app-header />
    <button (click)="users.login('Asha')">Log in as Asha</button>
    <button (click)="users.logout()">Log out</button>
    <p>Profile page: {{ users.user | async }}</p>
  `,
})
export class App {
  users = inject(UserStore);
}
```

Click **Log in as Asha**. The header and the profile page both show the new name, because both read the same `BehaviorSubject`.

## Signal-Based State

You can store the same state in signals. This is the recommended way in new Angular code, because the template reads a signal directly, without the `async` pipe.

- Keep the writable signal **private**.
- Give components a read-only signal with `asReadonly()`.
- Use `computed()` for values that come from the state, such as a count or a total.

```angular runnable title="State in signals"
import { Component, Injectable, computed, inject, signal } from '@angular/core';

export interface CartItem {
  name: string;
  price: number;
}

@Injectable({ providedIn: 'root' })
export class CartStore {
  private readonly _items = signal<CartItem[]>([]);

  readonly items = this._items.asReadonly();
  readonly count = computed(() => this._items().length);
  readonly total = computed(() => this._items().reduce((sum, i) => sum + i.price, 0));

  add(item: CartItem) {
    this._items.update(list => [...list, item]);
  }

  clear() {
    this._items.set([]);
  }
}

@Component({
  selector: 'app-cart-badge',
  template: `<p>🛒 {{ cart.count() }} items</p>`,
})
export class CartBadge {
  cart = inject(CartStore);
}

@Component({
  selector: 'app-checkout',
  template: `<p>Checkout total: {{ cart.total() }}</p>`,
})
export class Checkout {
  cart = inject(CartStore);
}

@Component({
  selector: 'app-root',
  imports: [CartBadge, Checkout],
  template: `
    <app-cart-badge />
    <button (click)="cart.add({ name: 'Mouse', price: 15 })">Add mouse ($15)</button>
    <button (click)="cart.add({ name: 'Keyboard', price: 40 })">Add keyboard ($40)</button>
    <button (click)="cart.clear()">Clear</button>
    <app-checkout />
  `,
})
export class App {
  cart = inject(CartStore);
}
```

The badge and the checkout read the same `CartStore`. Each click changes the private `_items` signal, and both `count` and `total` recalculate.

| Point | BehaviorSubject state | Signal state |
|---|---|---|
| Read in the template | `{{ store.user \| async }}` | `{{ store.user() }}` |
| Read in the class | `subscribe()` | `store.user()` |
| Values from the state | RxJS operators such as `map()` | `computed()` |
| Best for | Code that already uses RxJS | New code |

## State Management Patterns

A **pattern** is a common, tested way to organize code. These are the usual patterns for state in Angular apps:

- **Local state:** a signal in the component, for data only that component needs.
- **Store service:** one service per feature, for example `CartStore` and `UserStore`, with private state, read-only access and methods. Most apps need only this pattern.
- **One-way data flow:** components never change state directly. They call a method, the store changes the state, and the screen shows the new state.
- **A state library such as NgRx:** for large apps with many developers and complex rules. It is explained in the next section.

Start with local state and store services. Choose a library such as NgRx only when many features share state and the team needs strict rules for every change.

## NgRx Overview

**NgRx** is a popular library for state management in large Angular apps. It is not part of Angular, so you install it separately:

```bash
ng add @ngrx/store
```

NgRx uses the one-way data flow pattern with strict rules. It has four main parts.

<figure className="ngc-fig">
<svg viewBox="0 0 640 182" role="img" aria-label="NgRx data flow: a component dispatches an action, the reducer returns new state, the store holds it, and a selector gives data back to the component.">
<defs><marker id="ngc-n1" viewBox="0 0 10 10" refX="9" refY="5" markerWidth="7" markerHeight="7" orient="auto-start-reverse"><path d="M0 0L10 5L0 10z" className="ngc-arrowhead" /></marker></defs><g className="ngc-step ngc-t1"><g className="ngc-node ngc-soft c-blue"><rect x="4" y="40" width="115" height="60" rx="9" /><text x="61.5" y="67" textAnchor="middle" style={{fontSize:"14px"}}>Component</text><text className="ngc-sub" x="61.5" y="85" textAnchor="middle">dispatch(action)</text></g></g><line className="ngc-link ngc-flow c-slate" x1="121" y1="70" x2="129" y2="70" markerEnd="url(#ngc-n1)" /><g className="ngc-step ngc-t2"><g className="ngc-node ngc-soft c-orange"><rect x="131" y="40" width="115" height="60" rx="9" /><text x="188.5" y="67" textAnchor="middle" style={{fontSize:"14px"}}>Action</text><text className="ngc-sub" x="188.5" y="85" textAnchor="middle">addItem</text></g></g><line className="ngc-link ngc-flow c-slate" x1="248" y1="70" x2="256" y2="70" markerEnd="url(#ngc-n1)" /><g className="ngc-step ngc-t3"><g className="ngc-node ngc-soft c-purple"><rect x="258" y="40" width="115" height="60" rx="9" /><text x="315.5" y="67" textAnchor="middle" style={{fontSize:"14px"}}>Reducer</text><text className="ngc-sub" x="315.5" y="85" textAnchor="middle">returns new state</text></g></g><line className="ngc-link ngc-flow c-slate" x1="375" y1="70" x2="383" y2="70" markerEnd="url(#ngc-n1)" /><g className="ngc-step ngc-t4"><g className="ngc-node ngc-soft c-green"><rect x="385" y="40" width="115" height="60" rx="9" /><text x="442.5" y="67" textAnchor="middle" style={{fontSize:"14px"}}>Store</text><text className="ngc-sub" x="442.5" y="85" textAnchor="middle">holds the state</text></g></g><line className="ngc-link ngc-flow c-slate" x1="502" y1="70" x2="510" y2="70" markerEnd="url(#ngc-n1)" /><g className="ngc-step ngc-t5"><g className="ngc-node ngc-soft c-red"><rect x="512" y="40" width="115" height="60" rx="9" /><text x="569.5" y="67" textAnchor="middle" style={{fontSize:"14px"}}>Selector</text><text className="ngc-sub" x="569.5" y="85" textAnchor="middle">reads a part</text></g></g><path className="ngc-link ngc-flow c-slate" d="M572 102 C572 150 60 150 60 102" fill="none" markerEnd="url(#ngc-n1)" /><text className="ngc-note" x="320" y="170" textAnchor="middle">The selector gives the new data back to the component, and the screen updates.</text>
</svg>
<figcaption>NgRx data flow. Data always moves in one direction.</figcaption>
</figure>

### NgRx Store

The **store** is one object that holds the state of the whole app. Components cannot change it directly. They read it with selectors and request changes with actions.

### Actions

An **action** is an object that describes **what happened**, for example "an item was added to the cart". You create action types with `createAction()`:

```ts
import { createAction, props } from '@ngrx/store';

export const addItem = createAction('[Cart] Add Item', props<{ name: string }>());
export const clearCart = createAction('[Cart] Clear');
```

The text `'[Cart] Add Item'` is the name of the action. `props` describes the extra data that the action carries.

### Reducers

A **reducer** is a function that gets the current state and an action, and returns the **new** state. It never changes the old state; it creates a new one, the same as `update()` with signals.

```ts
import { createReducer, on } from '@ngrx/store';

export interface CartState {
  items: string[];
}

const initialState: CartState = { items: [] };

export const cartReducer = createReducer(
  initialState,
  on(addItem, (state, action) => ({ items: [...state.items, action.name] })),
  on(clearCart, () => ({ items: [] })),
);
```

### Selectors

A **selector** is a function that reads one part of the state, for example the number of items. It works like a computed signal: it recalculates only when its part of the state changes.

```ts
import { createFeatureSelector, createSelector } from '@ngrx/store';

export const selectCart = createFeatureSelector<CartState>('cart');
export const selectCount = createSelector(selectCart, cart => cart.items.length);
```

### Basic NgRx Data Flow

The parts work together in one direction:

1. The user clicks a button, and the component **dispatches** (sends) an action: `store.dispatch(addItem({ name: 'Pen' }))`.
2. The **reducer** gets the action and returns the new state.
3. The **store** saves the new state.
4. A **selector** gives the new value to the component: `store.selectSignal(selectCount)` returns it as a signal.
5. The screen shows the new value.

The practice blocks do not include NgRx. But you can see the same flow with only the Angular you already know: one signal holds the state, a reducer function creates the new state, and computed signals act as selectors.

```angular runnable title="The NgRx data flow in plain Angular"
import { Component, Injectable, computed, inject, signal } from '@angular/core';

// Actions: objects that describe what happened
export type CartAction =
  | { type: 'add', name: string }
  | { type: 'clear' };

export interface CartState {
  items: string[];
}

// Reducer: current state + action → new state
function cartReducer(state: CartState, action: CartAction): CartState {
  switch (action.type) {
    case 'add':
      return { items: [...state.items, action.name] };
    case 'clear':
      return { items: [] };
  }
}

@Injectable({ providedIn: 'root' })
export class Store {
  // Store: holds the state
  private readonly state = signal<CartState>({ items: [] });

  // Selectors: read one part of the state
  readonly items = computed(() => this.state().items);
  readonly count = computed(() => this.state().items.length);

  dispatch(action: CartAction) {
    this.state.update(current => cartReducer(current, action));
  }
}

@Component({
  selector: 'app-root',
  template: `
    <button (click)="store.dispatch({ type: 'add', name: 'Pen' })">Add pen</button>
    <button (click)="store.dispatch({ type: 'clear' })">Clear</button>
    <p>Count: {{ store.count() }}</p>
    <p>Items: {{ store.items().join(', ') }}</p>
  `,
})
export class App {
  store = inject(Store);
}
```

The component only sends actions and reads selectors. It never changes the state itself. Real NgRx works the same way, with more tools for large apps.

`export type CartAction = A | B` is a TypeScript **type alias** that lists the allowed action objects. The `|` means "or": an action is either an `add` object or a `clear` object.

## Common Mistakes

<div className="ngc-mistakes">

| Mistake | What you see | Fix |
|---|---|---|
| Each component stores its own copy of shared data | Different screens show different values | Store shared data in one service |
| Public writable signal or `BehaviorSubject` in a store | Any component can change the state, and bugs are hard to find | Keep it private; give `asReadonly()` or `asObservable()` |
| Putting component-only data in a shared service | The service grows, and other screens are affected | Keep it as component state |
| Changing state objects directly, for example `state.items.push(x)` | Computed signals and selectors keep old values | Return a new object |
| Adding NgRx to a small app | Many extra files for simple changes | Use store services; add NgRx when the app needs it |

</div>

## Check Yourself

## Exercises

## What to Remember

- Store data that one component needs in that component. Store shared data in one service, keep it private, and change it only with the service's methods.
- A service can hold state in a `BehaviorSubject` (read with the `async` pipe) or in signals (read with `()`). Signals are the simpler choice for new code.
- NgRx uses one-way data flow: components dispatch actions, reducers return new state, the store holds it, and selectors give it back to components.

Next: **Client-Side Rendering**, how Angular creates the page in the browser.
