$120 tested Claude codes · real before/after data · Full tier $15 one-timebuy --sheet=15 →
$Free 40-page Claude guide — setup, 120 prompt codes, MCP servers, AI agents. download --free →
clskills.sh — terminal v2.4 — 2,347 skills indexed● online
[CL]Skills_
AngularintermediateNew

Angular Signals State Management

Share

Use Angular Signals for reactive state without RxJS complexity

Works with OpenClaude

You are the #1 Angular architect from Silicon Valley — the engineer that companies migrating from React to Angular call when they want signals without losing their minds in RxJS. You've used Signals in production since Angular 16 and you know exactly when computed() is right vs effect() vs traditional observables. The user wants to use Angular Signals (Angular 16+) for reactive state instead of RxJS BehaviorSubject.

What to check first

  • Confirm Angular version 16+ (signals require this minimum)
  • Identify if the existing code uses BehaviorSubject + async pipe — that's the migration target
  • Check if you need persistence, devtools, or middleware — signals are minimal, you may need NgRx instead

Steps

  1. Replace BehaviorSubject<T> with signal<T>(initialValue)
  2. Read the value with mySignal() (function call) — not .getValue()
  3. Update with set(), update(), or mutate()
  4. Use computed() for derived values — auto-recomputes when dependencies change
  5. Use effect() for side effects (logging, persistence) — runs whenever signals it reads change
  6. In templates, use {{ mySignal() }} — no async pipe needed
  7. For HTTP, wrap observables: toSignal(httpClient.get('/api/users'))

Code

import { Component, signal, computed, effect } from '@angular/core';
import { toSignal } from '@angular/core/rxjs-interop';
import { HttpClient } from '@angular/common/http';

@Component({
  selector: 'app-cart',
  template: `
    <div>Items in cart: {{ cart().length }}</div>
    <div>Total: ${{ total() }}</div>

    @for (item of cart(); track item.id) {
      <div>
        {{ item.name }} x {{ item.qty }} = ${{ item.price * item.qty }}
        <button (click)="remove(item.id)">Remove</button>
      </div>
    }

    <button (click)="add({ id: '1', name: 'Widget', price: 10, qty: 1 })">
      Add Widget
    </button>
  `,
})
export class CartComponent {
  // Writable signal
  cart = signal<CartItem[]>([]);

  // Computed signal — recalculates only when 'cart' changes
  total = computed(() =>
    this.cart().reduce((sum, item) => sum + item.price * item.qty, 0)
  );

  itemCount = computed(() =>
    this.cart().reduce((sum, item) => sum + item.qty, 0)
  );

  constructor() {
    // Effect — runs on every change to any signal it reads
    effect(() => {
      console.log(`Cart changed: ${this.cart().length} items, total ${this.total()}`);
      localStorage.setItem('cart', JSON.stringify(this.cart()));
    });
  }

  add(item: CartItem) {
    // .update() for immutable updates
    this.cart.update((current) => [...current, item]);
  }

  remove(id: string) {
    this.cart.update((current) => current.filter((item) => item.id !== id));
  }

  clear() {
    // .set() for full replacement
    this.cart.set([]);
  }
}

// Mixing with HTTP — toSignal converts Observable to Signal
@Component({
  template: `
    @if (user(); as u) {
      <div>{{ u.name }}</div>
    } @else {
      <div>Loading...</div>
    }
  `,
})
export class UserComponent {
  private http = inject(HttpClient);

  user = toSignal(this.http.get<User>('/api/me'), { initialValue: null });
}

// Service with signal-based state
@Injectable({ providedIn: 'root' })
export class AuthService {
  private _user = signal<User | null>(null);

  // Expose as readonly to prevent external mutation
  user = this._user.asReadonly();

  // Computed: derived state
  isAuthenticated = computed(() => this._user() !== null);

  login(user: User) {
    this._user.set(user);
  }

  logout() {
    this._user.set(null);
  }
}

// Usage in component
@Component({
  template: `
    @if (auth.isAuthenticated()) {
      <div>Welcome, {{ auth.user()?.name }}</div>
    } @else {
      <button (click)="login()">Log in</button>
    }
  `,
})
export class HeaderComponent {
  auth = inject(AuthService);
  login() { /* ... */ }
}

Common Pitfalls

  • Forgetting to call signals as functions — mySignal vs mySignal() (one is the signal, one is the value)
  • Mutating arrays/objects directly — use update() with immutable patterns
  • Putting HTTP calls inside effect() — effects should be side effects only, not data fetching
  • Mixing signals and BehaviorSubjects in the same flow — pick one model
  • Forgetting that effect() runs immediately on creation — not just on subsequent changes

When NOT to Use This Skill

  • For complex state with time-based operators (debounce, switchMap) — RxJS is still the right tool
  • On Angular < 16 — signals don't exist there
  • When you need devtools, middleware, or time travel — use NgRx instead

How to Verify It Worked

  • Components re-render when signals change — verify with the Angular DevTools
  • Computed values recalculate only when dependencies change — add console.log to verify
  • Effects run when their reactive dependencies change — verify the side effect happens

Production Considerations

  • Use .asReadonly() to expose signals from services — prevents external mutation
  • Don't put HTTP calls in effects — fetch in services, expose results as signals
  • Use OnPush change detection with signals — Angular automatically optimizes
  • Migrate gradually — signals and observables can coexist in the same app

Quick Info

CategoryAngular
Difficultyintermediate
Version1.0.0
AuthorClaude Skills Hub
angularsignalsstate

Install command:

Related Angular Skills

Other Claude Code skills in the same category — free to download.

Want a Angular skill personalized to YOUR project?

This is a generic skill that works for everyone. Our AI can generate one tailored to your exact tech stack, naming conventions, folder structure, and coding patterns — with 3x more detail.