S
Signals
Type-safe Angular Signals for reactive state management with computed values and effects.
Code
typescript
1// counter.component.ts - Basic signals2import { Component, signal, computed, effect } from "@angular/core";3 4@Component({5 selector: "app-counter",6 standalone: true,7 template: `8 <div>9 <h2>Count: {{ count() }}</h2>10 <h3>Doubled: {{ doubledCount() }}</h3>11 <p>{{ message() }}</p>12 13 <button (click)="increment()">+</button>14 <button (click)="decrement()">-</button>15 <button (click)="reset()">Reset</button>16 </div>17 `,18})19export class CounterComponent {20 // Writable signal with initial value21 count = signal(0);22 23 // Computed signal (derived state)24 doubledCount = computed(() => this.count() * 2);25 26 message = computed(() => {27 const c = this.count();28 if (c === 0) return "Start counting!";29 if (c > 10) return "That's a lot!";30 if (c < 0) return "Going negative?";31 return `Current count: ${c}`;32 });33 34 constructor() {35 // Effect runs whenever signals it reads change36 effect(() => {37 console.log(`Count changed to: ${this.count()}`);38 // Side effects: logging, localStorage, etc.39 localStorage.setItem("count", this.count().toString());40 });41 }42 43 increment() {44 this.count.update((c) => c + 1);45 }46 47 decrement() {48 this.count.update((c) => c - 1);49 }50 51 reset() {52 this.count.set(0);53 }54}55 56// user-store.ts - State management with signals57import { Injectable, signal, computed } from "@angular/core";58 59interface User {60 id: number;61 name: string;62 email: string;63}64 65interface UserState {66 users: User[];67 selectedUserId: number | null;68 loading: boolean;69 error: string | null;70}71 72@Injectable({73 providedIn: "root",74})75export class UserStore {76 // Private writable state77 private state = signal<UserState>({78 users: [],79 selectedUserId: null,80 loading: false,81 error: null,82 });83 84 // Public readonly selectors (computed signals)85 readonly users = computed(() => this.state().users);86 readonly loading = computed(() => this.state().loading);87 readonly error = computed(() => this.state().error);88 89 readonly selectedUser = computed(() => {90 const state = this.state();91 return state.users.find((u) => u.id === state.selectedUserId) ?? null;92 });93 94 readonly userCount = computed(() => this.state().users.length);95 96 readonly adminUsers = computed(() =>97 this.state().users.filter((u) => u.email.includes("admin"))98 );99 100 // Actions101 setLoading(loading: boolean) {102 this.state.update((s) => ({ ...s, loading }));103 }104 105 setError(error: string | null) {106 this.state.update((s) => ({ ...s, error, loading: false }));107 }108 109 setUsers(users: User[]) {110 this.state.update((s) => ({ ...s, users, loading: false, error: null }));111 }112 113 addUser(user: User) {114 this.state.update((s) => ({115 ...s,116 users: [...s.users, user],117 }));118 }119 120 updateUser(id: number, updates: Partial<User>) {121 this.state.update((s) => ({122 ...s,123 users: s.users.map((u) => (u.id === id ? { ...u, ...updates } : u)),124 }));125 }126 127 removeUser(id: number) {128 this.state.update((s) => ({129 ...s,130 users: s.users.filter((u) => u.id !== id),131 selectedUserId: s.selectedUserId === id ? null : s.selectedUserId,132 }));133 }134 135 selectUser(id: number | null) {136 this.state.update((s) => ({ ...s, selectedUserId: id }));137 }138}139 140// user-list.component.ts - Using the store141@Component({142 selector: "app-user-list",143 standalone: true,144 template: `145 @if (store.loading()) {146 <p>Loading...</p>147 }148 149 @if (store.error()) {150 <p class="error">{{ store.error() }}</p>151 }152 153 <p>Total users: {{ store.userCount() }}</p>154 155 <ul>156 @for (user of store.users(); track user.id) {157 <li158 [class.selected]="store.selectedUser()?.id === user.id"159 (click)="store.selectUser(user.id)"160 >161 {{ user.name }} ({{ user.email }})162 </li>163 }164 </ul>165 166 @if (store.selectedUser(); as user) {167 <div class="details">168 <h3>Selected: {{ user.name }}</h3>169 <p>{{ user.email }}</p>170 </div>171 }172 `,173})174export class UserListComponent {175 constructor(public store: UserStore) {}176}Run this example locally
$ ng new my-app --strict