Upgrade to Pro — share decks privately, control downloads, hide ads and more …

Mastering State Management in Angular with the ...

Mastering State Management in Angular with the NgRx Signal Store

Signals are making their way into Angular and with them a new kind of state management with the NgRx Signal Store. A new state management, based on Signals, offers a lean, declarative, and reactive approach to state management. In this presentation, Fabian Gosebrink explains how and when the new NgRx Signal Store can be used, how it works, and what advantages it brings. After the presentation, state management in your application should no longer be a problem.

Fabian Gosebrink

November 12, 2024
Tweet

More Decks by Fabian Gosebrink

Other Decks in Technology

Transcript

  1. todos = signal<Todo[]>([]); todos(); todos.set(...); todos.update((items) => { /* ...

    */ }); 1 2 3 4 5 6 7 todos = signal<Todo[]>([]); 1 2 todos(); 3 4 todos.set(...); 5 6 todos.update((items) => { /* ... */ }); 7
  2. todos = signal<Todo[]>([]); todos(); todos.set(...); todos.update((items) => { /* ...

    */ }); 1 2 3 4 5 6 7 todos = signal<Todo[]>([]); 1 2 todos(); 3 4 todos.set(...); 5 6 todos.update((items) => { /* ... */ }); 7 todos(); todos = signal<Todo[]>([]); 1 2 3 4 todos.set(...); 5 6 todos.update((items) => { /* ... */ }); 7
  3. todos = signal<Todo[]>([]); todos(); todos.set(...); todos.update((items) => { /* ...

    */ }); 1 2 3 4 5 6 7 todos = signal<Todo[]>([]); 1 2 todos(); 3 4 todos.set(...); 5 6 todos.update((items) => { /* ... */ }); 7 todos(); todos = signal<Todo[]>([]); 1 2 3 4 todos.set(...); 5 6 todos.update((items) => { /* ... */ }); 7 todos.set(...); todos = signal<Todo[]>([]); 1 2 todos(); 3 4 5 6 todos.update((items) => { /* ... */ }); 7
  4. todos = signal<Todo[]>([]); todos(); todos.set(...); todos.update((items) => { /* ...

    */ }); 1 2 3 4 5 6 7 todos = signal<Todo[]>([]); 1 2 todos(); 3 4 todos.set(...); 5 6 todos.update((items) => { /* ... */ }); 7 todos(); todos = signal<Todo[]>([]); 1 2 3 4 todos.set(...); 5 6 todos.update((items) => { /* ... */ }); 7 todos.set(...); todos = signal<Todo[]>([]); 1 2 todos(); 3 4 5 6 todos.update((items) => { /* ... */ }); 7 todos.update((items) => { /* ... */ }); todos = signal<Todo[]>([]); 1 2 todos(); 3 4 todos.set(...); 5 6 7
  5. todos = signal<Todo[]>([]); count = computed(() => todos().length); count =

    linkedSignal(() => todos.length); effect(() => { console.log('Todos count changed to ', count()); }); 1 2 3 4 5 6 7 8 9
  6. todos = signal<Todo[]>([]); count = computed(() => todos().length); count =

    linkedSignal(() => todos.length); effect(() => { console.log('Todos count changed to ', count()); }); 1 2 3 4 5 6 7 8 9 count = computed(() => todos().length); todos = signal<Todo[]>([]); 1 2 3 4 count = linkedSignal(() => todos.length); 5 6 effect(() => { 7 console.log('Todos count changed to ', count()); 8 }); 9
  7. todos = signal<Todo[]>([]); count = computed(() => todos().length); count =

    linkedSignal(() => todos.length); effect(() => { console.log('Todos count changed to ', count()); }); 1 2 3 4 5 6 7 8 9 count = computed(() => todos().length); todos = signal<Todo[]>([]); 1 2 3 4 count = linkedSignal(() => todos.length); 5 6 effect(() => { 7 console.log('Todos count changed to ', count()); 8 }); 9 count = linkedSignal(() => todos.length); todos = signal<Todo[]>([]); 1 2 count = computed(() => todos().length); 3 4 5 6 effect(() => { 7 console.log('Todos count changed to ', count()); 8 }); 9
  8. todos = signal<Todo[]>([]); count = computed(() => todos().length); count =

    linkedSignal(() => todos.length); effect(() => { console.log('Todos count changed to ', count()); }); 1 2 3 4 5 6 7 8 9 count = computed(() => todos().length); todos = signal<Todo[]>([]); 1 2 3 4 count = linkedSignal(() => todos.length); 5 6 effect(() => { 7 console.log('Todos count changed to ', count()); 8 }); 9 count = linkedSignal(() => todos.length); todos = signal<Todo[]>([]); 1 2 count = computed(() => todos().length); 3 4 5 6 effect(() => { 7 console.log('Todos count changed to ', count()); 8 }); 9 effect(() => { console.log('Todos count changed to ', count()); }); todos = signal<Todo[]>([]); 1 2 count = computed(() => todos().length); 3 4 count = linkedSignal(() => todos.length); 5 6 7 8 9
  9. export class TodoMainComponent implements OnInit { private url = `${environment.apiUrl}todos`;

    private http = inject(HttpClient); private todos = signal<Todo[]>([]); count = computed(() => this.todos().length); doneItems = computed(() => this.todos().filter((item) => item.done)?.length); openItems = computed(() => this.todos().filter((item) => !item.done)?.length); sortedTodos = computed(() => this.todos().sort((b, a) => +b.done - +a.done)); // Method logic with http... } 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15
  10. export class TodoMainComponent implements OnInit { private url = `${environment.apiUrl}todos`;

    private http = inject(HttpClient); private todos = signal<Todo[]>([]); count = computed(() => this.todos().length); doneItems = computed(() => this.todos().filter((item) => item.done)?.length); openItems = computed(() => this.todos().filter((item) => !item.done)?.length); sortedTodos = computed(() => this.todos().sort((b, a) => +b.done - +a.done)); // Method logic with http... } 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 private url = `${environment.apiUrl}todos`; private http = inject(HttpClient); private todos = signal<Todo[]>([]); export class TodoMainComponent implements OnInit { 1 2 3 4 5 count = computed(() => this.todos().length); 6 7 doneItems = computed(() => this.todos().filter((item) => item.done)?.length); 8 9 openItems = computed(() => this.todos().filter((item) => !item.done)?.length); 10 11 sortedTodos = computed(() => this.todos().sort((b, a) => +b.done - +a.done)); 12 13 // Method logic with http... 14 } 15
  11. export class TodoMainComponent implements OnInit { private url = `${environment.apiUrl}todos`;

    private http = inject(HttpClient); private todos = signal<Todo[]>([]); count = computed(() => this.todos().length); doneItems = computed(() => this.todos().filter((item) => item.done)?.length); openItems = computed(() => this.todos().filter((item) => !item.done)?.length); sortedTodos = computed(() => this.todos().sort((b, a) => +b.done - +a.done)); // Method logic with http... } 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 private url = `${environment.apiUrl}todos`; private http = inject(HttpClient); private todos = signal<Todo[]>([]); export class TodoMainComponent implements OnInit { 1 2 3 4 5 count = computed(() => this.todos().length); 6 7 doneItems = computed(() => this.todos().filter((item) => item.done)?.length); 8 9 openItems = computed(() => this.todos().filter((item) => !item.done)?.length); 10 11 sortedTodos = computed(() => this.todos().sort((b, a) => +b.done - +a.done)); 12 13 // Method logic with http... 14 } 15 count = computed(() => this.todos().length); doneItems = computed(() => this.todos().filter((item) => item.done)?.length); openItems = computed(() => this.todos().filter((item) => !item.done)?.length); sortedTodos = computed(() => this.todos().sort((b, a) => +b.done - +a.done)); export class TodoMainComponent implements OnInit { 1 private url = `${environment.apiUrl}todos`; 2 private http = inject(HttpClient); 3 private todos = signal<Todo[]>([]); 4 5 6 7 8 9 10 11 12 13 // Method logic with http... 14 } 15
  12. @Injectable({ providedIn: 'root' }) export class TodoService { private url

    = `${environment.apiUrl}todos`; private http = inject(HttpClient); private todos = signal<Todo[]>([]); count = computed(() => this.todos().length); doneItems = computed(() => this.todos().filter((item) => item.done)?.length); openItems = computed(() => this.todos().filter((item) => !item.done)?.length); sortedTodos = computed(() => this.todos().sort((b, a) => +b.done - +a.done)); getItems() { this.http.get<Todo[]>(this.url).subscribe((todos) => this.todos.set(todos)); } replaceItem() { this.todos.update((items) => [...items]); } addItem(value: string) { this.http.post<Todo>(this.url, { value }).subscribe((addedTodo) => { this.todos.update((items) => [addedTodo, ...items]); }); } updateItem(value: Todo) { // ... } removeItem(id: string) { this.http.delete(`${this.url}/${id}`).subscribe(() => { this.todos.update((items) => [...items.filter((item) => item.id !== id)]); }); } } 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35
  13. @Injectable({ providedIn: 'root' }) export class TodoService { private url

    = `${environment.apiUrl}todos`; private http = inject(HttpClient); private todos = signal<Todo[]>([]); count = computed(() => this.todos().length); doneItems = computed(() => this.todos().filter((item) => item.done)?.length); openItems = computed(() => this.todos().filter((item) => !item.done)?.length); sortedTodos = computed(() => this.todos().sort((b, a) => +b.done - +a.done)); getItems() { this.http.get<Todo[]>(this.url).subscribe((todos) => this.todos.set(todos)); } replaceItem() { this.todos.update((items) => [...items]); } addItem(value: string) { this.http.post<Todo>(this.url, { value }).subscribe((addedTodo) => { this.todos.update((items) => [addedTodo, ...items]); }); } updateItem(value: Todo) { // ... } removeItem(id: string) { this.http.delete(`${this.url}/${id}`).subscribe(() => { this.todos.update((items) => [...items.filter((item) => item.id !== id)]); }); } } 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 private url = `${environment.apiUrl}todos`; private http = inject(HttpClient); private todos = signal<Todo[]>([]); @Injectable({ providedIn: 'root' }) 1 export class TodoService { 2 3 4 5 6 count = computed(() => this.todos().length); 7 doneItems = computed(() => this.todos().filter((item) => item.done)?.length); 8 openItems = computed(() => this.todos().filter((item) => !item.done)?.length); 9 sortedTodos = computed(() => this.todos().sort((b, a) => +b.done - +a.done)); 10 11 getItems() { 12 this.http.get<Todo[]>(this.url).subscribe((todos) => this.todos.set(todos)); 13 } 14 15 replaceItem() { 16 this.todos.update((items) => [...items]); 17 } 18 19 addItem(value: string) { 20 this.http.post<Todo>(this.url, { value }).subscribe((addedTodo) => { 21 this.todos.update((items) => [addedTodo, ...items]); 22 }); 23 } 24 25 updateItem(value: Todo) { 26 // ... 27 } 28 29 removeItem(id: string) { 30 this.http.delete(`${this.url}/${id}`).subscribe(() => { 31 this.todos.update((items) => [...items.filter((item) => item.id !== id)]); 32 }); 33 } 34 } 35
  14. @Injectable({ providedIn: 'root' }) export class TodoService { private url

    = `${environment.apiUrl}todos`; private http = inject(HttpClient); private todos = signal<Todo[]>([]); count = computed(() => this.todos().length); doneItems = computed(() => this.todos().filter((item) => item.done)?.length); openItems = computed(() => this.todos().filter((item) => !item.done)?.length); sortedTodos = computed(() => this.todos().sort((b, a) => +b.done - +a.done)); getItems() { this.http.get<Todo[]>(this.url).subscribe((todos) => this.todos.set(todos)); } replaceItem() { this.todos.update((items) => [...items]); } addItem(value: string) { this.http.post<Todo>(this.url, { value }).subscribe((addedTodo) => { this.todos.update((items) => [addedTodo, ...items]); }); } updateItem(value: Todo) { // ... } removeItem(id: string) { this.http.delete(`${this.url}/${id}`).subscribe(() => { this.todos.update((items) => [...items.filter((item) => item.id !== id)]); }); } } 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 private url = `${environment.apiUrl}todos`; private http = inject(HttpClient); private todos = signal<Todo[]>([]); @Injectable({ providedIn: 'root' }) 1 export class TodoService { 2 3 4 5 6 count = computed(() => this.todos().length); 7 doneItems = computed(() => this.todos().filter((item) => item.done)?.length); 8 openItems = computed(() => this.todos().filter((item) => !item.done)?.length); 9 sortedTodos = computed(() => this.todos().sort((b, a) => +b.done - +a.done)); 10 11 getItems() { 12 this.http.get<Todo[]>(this.url).subscribe((todos) => this.todos.set(todos)); 13 } 14 15 replaceItem() { 16 this.todos.update((items) => [...items]); 17 } 18 19 addItem(value: string) { 20 this.http.post<Todo>(this.url, { value }).subscribe((addedTodo) => { 21 this.todos.update((items) => [addedTodo, ...items]); 22 }); 23 } 24 25 updateItem(value: Todo) { 26 // ... 27 } 28 29 removeItem(id: string) { 30 this.http.delete(`${this.url}/${id}`).subscribe(() => { 31 this.todos.update((items) => [...items.filter((item) => item.id !== id)]); 32 }); 33 } 34 } 35 count = computed(() => this.todos().length); doneItems = computed(() => this.todos().filter((item) => item.done)?.length); openItems = computed(() => this.todos().filter((item) => !item.done)?.length); sortedTodos = computed(() => this.todos().sort((b, a) => +b.done - +a.done)); @Injectable({ providedIn: 'root' }) 1 export class TodoService { 2 private url = `${environment.apiUrl}todos`; 3 private http = inject(HttpClient); 4 private todos = signal<Todo[]>([]); 5 6 7 8 9 10 11 getItems() { 12 this.http.get<Todo[]>(this.url).subscribe((todos) => this.todos.set(todos)); 13 } 14 15 replaceItem() { 16 this.todos.update((items) => [...items]); 17 } 18 19 addItem(value: string) { 20 this.http.post<Todo>(this.url, { value }).subscribe((addedTodo) => { 21 this.todos.update((items) => [addedTodo, ...items]); 22 }); 23 } 24 25 updateItem(value: Todo) { 26 // ... 27 } 28 29 removeItem(id: string) { 30 this.http.delete(`${this.url}/${id}`).subscribe(() => { 31 this.todos.update((items) => [...items.filter((item) => item.id !== id)]); 32 }); 33 } 34 } 35
  15. @Injectable({ providedIn: 'root' }) export class TodoService { private url

    = `${environment.apiUrl}todos`; private http = inject(HttpClient); private todos = signal<Todo[]>([]); count = computed(() => this.todos().length); doneItems = computed(() => this.todos().filter((item) => item.done)?.length); openItems = computed(() => this.todos().filter((item) => !item.done)?.length); sortedTodos = computed(() => this.todos().sort((b, a) => +b.done - +a.done)); getItems() { this.http.get<Todo[]>(this.url).subscribe((todos) => this.todos.set(todos)); } replaceItem() { this.todos.update((items) => [...items]); } addItem(value: string) { this.http.post<Todo>(this.url, { value }).subscribe((addedTodo) => { this.todos.update((items) => [addedTodo, ...items]); }); } updateItem(value: Todo) { // ... } removeItem(id: string) { this.http.delete(`${this.url}/${id}`).subscribe(() => { this.todos.update((items) => [...items.filter((item) => item.id !== id)]); }); } } 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 private url = `${environment.apiUrl}todos`; private http = inject(HttpClient); private todos = signal<Todo[]>([]); @Injectable({ providedIn: 'root' }) 1 export class TodoService { 2 3 4 5 6 count = computed(() => this.todos().length); 7 doneItems = computed(() => this.todos().filter((item) => item.done)?.length); 8 openItems = computed(() => this.todos().filter((item) => !item.done)?.length); 9 sortedTodos = computed(() => this.todos().sort((b, a) => +b.done - +a.done)); 10 11 getItems() { 12 this.http.get<Todo[]>(this.url).subscribe((todos) => this.todos.set(todos)); 13 } 14 15 replaceItem() { 16 this.todos.update((items) => [...items]); 17 } 18 19 addItem(value: string) { 20 this.http.post<Todo>(this.url, { value }).subscribe((addedTodo) => { 21 this.todos.update((items) => [addedTodo, ...items]); 22 }); 23 } 24 25 updateItem(value: Todo) { 26 // ... 27 } 28 29 removeItem(id: string) { 30 this.http.delete(`${this.url}/${id}`).subscribe(() => { 31 this.todos.update((items) => [...items.filter((item) => item.id !== id)]); 32 }); 33 } 34 } 35 count = computed(() => this.todos().length); doneItems = computed(() => this.todos().filter((item) => item.done)?.length); openItems = computed(() => this.todos().filter((item) => !item.done)?.length); sortedTodos = computed(() => this.todos().sort((b, a) => +b.done - +a.done)); @Injectable({ providedIn: 'root' }) 1 export class TodoService { 2 private url = `${environment.apiUrl}todos`; 3 private http = inject(HttpClient); 4 private todos = signal<Todo[]>([]); 5 6 7 8 9 10 11 getItems() { 12 this.http.get<Todo[]>(this.url).subscribe((todos) => this.todos.set(todos)); 13 } 14 15 replaceItem() { 16 this.todos.update((items) => [...items]); 17 } 18 19 addItem(value: string) { 20 this.http.post<Todo>(this.url, { value }).subscribe((addedTodo) => { 21 this.todos.update((items) => [addedTodo, ...items]); 22 }); 23 } 24 25 updateItem(value: Todo) { 26 // ... 27 } 28 29 removeItem(id: string) { 30 this.http.delete(`${this.url}/${id}`).subscribe(() => { 31 this.todos.update((items) => [...items.filter((item) => item.id !== id)]); 32 }); 33 } 34 } 35 getItems() { this.http.get<Todo[]>(this.url).subscribe((todos) => this.todos.set(todos)); } replaceItem() { this.todos.update((items) => [...items]); } addItem(value: string) { this.http.post<Todo>(this.url, { value }).subscribe((addedTodo) => { this.todos.update((items) => [addedTodo, ...items]); }); } updateItem(value: Todo) { // ... } removeItem(id: string) { this.http.delete(`${this.url}/${id}`).subscribe(() => { this.todos.update((items) => [...items.filter((item) => item.id !== id)]); }); } @Injectable({ providedIn: 'root' }) 1 export class TodoService { 2 private url = `${environment.apiUrl}todos`; 3 private http = inject(HttpClient); 4 private todos = signal<Todo[]>([]); 5 6 count = computed(() => this.todos().length); 7 doneItems = computed(() => this.todos().filter((item) => item.done)?.length); 8 openItems = computed(() => this.todos().filter((item) => !item.done)?.length); 9 sortedTodos = computed(() => this.todos().sort((b, a) => +b.done - +a.done)); 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 } 35
  16. export class TodoMainComponent implements OnInit { private todoService = inject(TodoService);

    count = this.todoService.count; doneItems = this.todoService.doneItems; openItems = this.todoService.openItems; sortedTodos = this.todoService.sortedTodos; ngOnInit(): void { this.todoService.getItems(); } addTodo(value: string) { this.todoService.addItem(value); } deleteTodo(item: Todo): void { this.todoService.removeitem(item.id); } markAsDone(item: Todo): void { this.todoService.updateItem(item); } } 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24
  17. @Component({ /* ... */ }) export class TodoMainComponent { private

    readonly store = inject(Store); items$ = this.store.select(getAllItems); ngOnInit(): void { this.store.dispatch(TodoActions.loadAllTodos()); } } 1 2 3 4 5 6 7 8 9 10
  18. @Component({ /* ... */ }) export class TodoMainComponent { private

    readonly store = inject(Store); items$ = this.store.select(getAllItems); ngOnInit(): void { this.store.dispatch(TodoActions.loadAllTodos()); } } 1 2 3 4 5 6 7 8 9 10 private readonly store = inject(Store); @Component({ /* ... */ }) 1 export class TodoMainComponent { 2 3 4 items$ = this.store.select(getAllItems); 5 6 ngOnInit(): void { 7 this.store.dispatch(TodoActions.loadAllTodos()); 8 } 9 } 10
  19. @Component({ /* ... */ }) export class TodoMainComponent { private

    readonly store = inject(Store); items$ = this.store.select(getAllItems); ngOnInit(): void { this.store.dispatch(TodoActions.loadAllTodos()); } } 1 2 3 4 5 6 7 8 9 10 private readonly store = inject(Store); @Component({ /* ... */ }) 1 export class TodoMainComponent { 2 3 4 items$ = this.store.select(getAllItems); 5 6 ngOnInit(): void { 7 this.store.dispatch(TodoActions.loadAllTodos()); 8 } 9 } 10 items$ = this.store.select(getAllItems); @Component({ /* ... */ }) 1 export class TodoMainComponent { 2 private readonly store = inject(Store); 3 4 5 6 ngOnInit(): void { 7 this.store.dispatch(TodoActions.loadAllTodos()); 8 } 9 } 10
  20. @Component({ /* ... */ }) export class TodoMainComponent { private

    readonly store = inject(Store); items$ = this.store.select(getAllItems); ngOnInit(): void { this.store.dispatch(TodoActions.loadAllTodos()); } } 1 2 3 4 5 6 7 8 9 10 private readonly store = inject(Store); @Component({ /* ... */ }) 1 export class TodoMainComponent { 2 3 4 items$ = this.store.select(getAllItems); 5 6 ngOnInit(): void { 7 this.store.dispatch(TodoActions.loadAllTodos()); 8 } 9 } 10 items$ = this.store.select(getAllItems); @Component({ /* ... */ }) 1 export class TodoMainComponent { 2 private readonly store = inject(Store); 3 4 5 6 ngOnInit(): void { 7 this.store.dispatch(TodoActions.loadAllTodos()); 8 } 9 } 10 this.store.dispatch(TodoActions.loadAllTodos()); @Component({ /* ... */ }) 1 export class TodoMainComponent { 2 private readonly store = inject(Store); 3 4 items$ = this.store.select(getAllItems); 5 6 ngOnInit(): void { 7 8 } 9 } 10
  21. @Component({ /* ... */ }) export class TodoMainComponent { private

    readonly store = inject(Store); items$ = this.store.select(getAllItems); ngOnInit(): void { this.store.dispatch(TodoActions.loadAllTodos()); } } 1 2 3 4 5 6 7 8 9 10 <app-todo-list [items]="items$ | async" ></app-todo-list> 1 2 3
  22. @Component({ /* ... */ }) export class TodoMainComponent { private

    readonly store = inject(Store); items$ = this.store.select(getAllItems); ngOnInit(): void { this.store.dispatch(TodoActions.loadAllTodos()); } } 1 2 3 4 5 6 7 8 9 10 items$ = this.store.select(getAllItems); @Component({ /* ... */ }) 1 export class TodoMainComponent { 2 private readonly store = inject(Store); 3 4 5 6 ngOnInit(): void { 7 this.store.dispatch(TodoActions.loadAllTodos()); 8 } 9 } 10 <app-todo-list [items]="items$ | async" ></app-todo-list> 1 2 3
  23. items = this.store.selectSignal(getAllItems); @Component({ /* ... */ }) 1 export

    class TodoMainComponent { 2 private readonly store = inject(Store); 3 4 5 6 ngOnInit(): void { 7 this.store.dispatch(TodoActions.loadAllTodos()); 8 } 9 } 10 <app-todo-list [items]="items()" ></app-todo-list> 1 2 3
  24. items = this.store.selectSignal(getAllItems); @Component({ /* ... */ }) 1 export

    class TodoMainComponent { 2 private readonly store = inject(Store); 3 4 5 6 ngOnInit(): void { 7 this.store.dispatch(TodoActions.loadAllTodos()); 8 } 9 } 10 @Component({ /* ... */ }) export class TodoMainComponent { private readonly store = inject(Store); items = this.store.selectSignal(getAllItems); ngOnInit(): void { this.store.dispatch(TodoActions.loadAllTodos()); } } 1 2 3 4 5 6 7 8 9 10 <app-todo-list [items]="items()" ></app-todo-list> 1 2 3
  25. export interface TodoState { items: Todo[]; loading: boolean; } export

    const initialState: TodoState = { items: [], loading: false, }; export const TodoStore = signalStore( withState(initialState), ); 1 2 3 4 5 6 7 8 9 10 11 12 13
  26. export interface TodoState { items: Todo[]; loading: boolean; } export

    const initialState: TodoState = { items: [], loading: false, }; export const TodoStore = signalStore( withState(initialState), ); 1 2 3 4 5 6 7 8 9 10 11 12 13 export interface TodoState { items: Todo[]; loading: boolean; } 1 2 3 4 5 export const initialState: TodoState = { 6 items: [], 7 loading: false, 8 }; 9 10 export const TodoStore = signalStore( 11 withState(initialState), 12 ); 13
  27. export interface TodoState { items: Todo[]; loading: boolean; } export

    const initialState: TodoState = { items: [], loading: false, }; export const TodoStore = signalStore( withState(initialState), ); 1 2 3 4 5 6 7 8 9 10 11 12 13 export interface TodoState { items: Todo[]; loading: boolean; } 1 2 3 4 5 export const initialState: TodoState = { 6 items: [], 7 loading: false, 8 }; 9 10 export const TodoStore = signalStore( 11 withState(initialState), 12 ); 13 export const initialState: TodoState = { items: [], loading: false, }; export interface TodoState { 1 items: Todo[]; 2 loading: boolean; 3 } 4 5 6 7 8 9 10 export const TodoStore = signalStore( 11 withState(initialState), 12 ); 13
  28. export interface TodoState { items: Todo[]; loading: boolean; } export

    const initialState: TodoState = { items: [], loading: false, }; export const TodoStore = signalStore( withState(initialState), ); 1 2 3 4 5 6 7 8 9 10 11 12 13 export interface TodoState { items: Todo[]; loading: boolean; } 1 2 3 4 5 export const initialState: TodoState = { 6 items: [], 7 loading: false, 8 }; 9 10 export const TodoStore = signalStore( 11 withState(initialState), 12 ); 13 export const initialState: TodoState = { items: [], loading: false, }; export interface TodoState { 1 items: Todo[]; 2 loading: boolean; 3 } 4 5 6 7 8 9 10 export const TodoStore = signalStore( 11 withState(initialState), 12 ); 13 export const TodoStore = signalStore( withState(initialState), ); export interface TodoState { 1 items: Todo[]; 2 loading: boolean; 3 } 4 5 export const initialState: TodoState = { 6 items: [], 7 loading: false, 8 }; 9 10 11 12 13
  29. export const TodoStore = signalStore( withState({ /* ... */ }),

    withMethods((...) => ({ // ... })) ); 1 2 3 4 5 6
  30. export const TodoStore = signalStore( withState({ /* ... */ }),

    withMethods((...) => ({ // ... })) ); 1 2 3 4 5 6 withMethods((...) => ({ // ... })) export const TodoStore = signalStore( 1 withState({ /* ... */ }), 2 3 4 5 ); 6
  31. export const TodoStore = signalStore( withState({ /* ... */ ),

    withMethods((store, todoService = inject(TodoService)) => ({ loadAllTodos() { // use Todoservice and then patchState(store, { items }); }, })) ); 1 2 3 4 5 6 7 8 9
  32. export const TodoStore = signalStore( withState({ /* ... */ ),

    withMethods((store, todoService = inject(TodoService)) => ({ loadAllTodos() { // use Todoservice and then patchState(store, { items }); }, })) ); 1 2 3 4 5 6 7 8 9 withMethods((store, todoService = inject(TodoService)) => ({ loadAllTodos() { // use Todoservice and then patchState(store, { items }); }, })) export const TodoStore = signalStore( 1 withState({ /* ... */ ), 2 3 4 5 6 7 8 ); 9
  33. export const TodoStore = signalStore( { providedIn: 'root' }, withState({

    /* state goes here */ ), withMethods((store, todoService = inject(TodoService)) => ({ loadAllTodos: rxMethod( switchMap(() => { patchState(store, {loading: true}); return todoService.getItems().pipe( tapResponse({ next: (items) => patchState(store, {items}), error: console.error, finalize: () => patchState(store, {loading: false}), }) ); }) ), })) ); 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19
  34. export const TodoStore = signalStore( { providedIn: 'root' }, withState({

    /* state goes here */ ), withMethods((store, todoService = inject(TodoService)) => ({ loadAllTodos: rxMethod( switchMap(() => { patchState(store, {loading: true}); return todoService.getItems().pipe( tapResponse({ next: (items) => patchState(store, {items}), error: console.error, finalize: () => patchState(store, {loading: false}), }) ); }) ), })) ); 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 loadAllTodos: rxMethod( switchMap(() => { patchState(store, {loading: true}); return todoService.getItems().pipe( tapResponse({ next: (items) => patchState(store, {items}), error: console.error, finalize: () => patchState(store, {loading: false}), }) ); }) ), export const TodoStore = signalStore( 1 { providedIn: 'root' }, 2 withState({ /* state goes here */ ), 3 withMethods((store, todoService = inject(TodoService)) => ({ 4 5 6 7 8 9 10 11 12 13 14 15 16 17 })) 18 ); 19
  35. export const TodoStore = signalStore( { providedIn: 'root' }, withState({

    /* state goes here */ ), withMethods((store, todoService = inject(TodoService)) => ({ loadAllTodos: rxMethod( switchMap(() => { patchState(store, {loading: true}); return todoService.getItems().pipe( tapResponse({ next: (items) => patchState(store, {items}), error: console.error, finalize: () => patchState(store, {loading: false}), }) ); }) ), })) ); 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 loadAllTodos: rxMethod( switchMap(() => { patchState(store, {loading: true}); return todoService.getItems().pipe( tapResponse({ next: (items) => patchState(store, {items}), error: console.error, finalize: () => patchState(store, {loading: false}), }) ); }) ), export const TodoStore = signalStore( 1 { providedIn: 'root' }, 2 withState({ /* state goes here */ ), 3 withMethods((store, todoService = inject(TodoService)) => ({ 4 5 6 7 8 9 10 11 12 13 14 15 16 17 })) 18 ); 19 patchState(store, {loading: true}); export const TodoStore = signalStore( 1 { providedIn: 'root' }, 2 withState({ /* state goes here */ ), 3 withMethods((store, todoService = inject(TodoService)) => ({ 4 loadAllTodos: rxMethod( 5 switchMap(() => { 6 7 8 return todoService.getItems().pipe( 9 tapResponse({ 10 next: (items) => patchState(store, {items}), 11 error: console.error, 12 finalize: () => patchState(store, {loading: false}), 13 }) 14 ); 15 }) 16 ), 17 })) 18 ); 19
  36. export const TodoStore = signalStore( { providedIn: 'root' }, withState({

    /* state goes here */ ), withMethods((store, todoService = inject(TodoService)) => ({ loadAllTodos: rxMethod( switchMap(() => { patchState(store, {loading: true}); return todoService.getItems().pipe( tapResponse({ next: (items) => patchState(store, {items}), error: console.error, finalize: () => patchState(store, {loading: false}), }) ); }) ), })) ); 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 loadAllTodos: rxMethod( switchMap(() => { patchState(store, {loading: true}); return todoService.getItems().pipe( tapResponse({ next: (items) => patchState(store, {items}), error: console.error, finalize: () => patchState(store, {loading: false}), }) ); }) ), export const TodoStore = signalStore( 1 { providedIn: 'root' }, 2 withState({ /* state goes here */ ), 3 withMethods((store, todoService = inject(TodoService)) => ({ 4 5 6 7 8 9 10 11 12 13 14 15 16 17 })) 18 ); 19 patchState(store, {loading: true}); export const TodoStore = signalStore( 1 { providedIn: 'root' }, 2 withState({ /* state goes here */ ), 3 withMethods((store, todoService = inject(TodoService)) => ({ 4 loadAllTodos: rxMethod( 5 switchMap(() => { 6 7 8 return todoService.getItems().pipe( 9 tapResponse({ 10 next: (items) => patchState(store, {items}), 11 error: console.error, 12 finalize: () => patchState(store, {loading: false}), 13 }) 14 ); 15 }) 16 ), 17 })) 18 ); 19 return todoService.getItems().pipe( tapResponse({ next: (items) => patchState(store, {items}), error: console.error, finalize: () => patchState(store, {loading: false}), }) ); export const TodoStore = signalStore( 1 { providedIn: 'root' }, 2 withState({ /* state goes here */ ), 3 withMethods((store, todoService = inject(TodoService)) => ({ 4 loadAllTodos: rxMethod( 5 switchMap(() => { 6 patchState(store, {loading: true}); 7 8 9 10 11 12 13 14 15 }) 16 ), 17 })) 18 ); 19
  37. export const TodoStore = signalStore( { providedIn: 'root' }, withState({

    /* state goes here */ ), withMethods((store, todoService = inject(TodoService)) => ({ loadAllTodos: rxMethod( switchMap(() => { patchState(store, {loading: true}); return todoService.getItems().pipe( tapResponse({ next: (items) => patchState(store, {items}), error: console.error, finalize: () => patchState(store, {loading: false}), }) ); }) ), })) ); 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 loadAllTodos: rxMethod( switchMap(() => { patchState(store, {loading: true}); return todoService.getItems().pipe( tapResponse({ next: (items) => patchState(store, {items}), error: console.error, finalize: () => patchState(store, {loading: false}), }) ); }) ), export const TodoStore = signalStore( 1 { providedIn: 'root' }, 2 withState({ /* state goes here */ ), 3 withMethods((store, todoService = inject(TodoService)) => ({ 4 5 6 7 8 9 10 11 12 13 14 15 16 17 })) 18 ); 19 patchState(store, {loading: true}); export const TodoStore = signalStore( 1 { providedIn: 'root' }, 2 withState({ /* state goes here */ ), 3 withMethods((store, todoService = inject(TodoService)) => ({ 4 loadAllTodos: rxMethod( 5 switchMap(() => { 6 7 8 return todoService.getItems().pipe( 9 tapResponse({ 10 next: (items) => patchState(store, {items}), 11 error: console.error, 12 finalize: () => patchState(store, {loading: false}), 13 }) 14 ); 15 }) 16 ), 17 })) 18 ); 19 return todoService.getItems().pipe( tapResponse({ next: (items) => patchState(store, {items}), error: console.error, finalize: () => patchState(store, {loading: false}), }) ); export const TodoStore = signalStore( 1 { providedIn: 'root' }, 2 withState({ /* state goes here */ ), 3 withMethods((store, todoService = inject(TodoService)) => ({ 4 loadAllTodos: rxMethod( 5 switchMap(() => { 6 patchState(store, {loading: true}); 7 8 9 10 11 12 13 14 15 }) 16 ), 17 })) 18 ); 19 tapResponse({ next: (items) => patchState(store, {items}), error: console.error, finalize: () => patchState(store, {loading: false}), }) export const TodoStore = signalStore( 1 { providedIn: 'root' }, 2 withState({ /* state goes here */ ), 3 withMethods((store, todoService = inject(TodoService)) => ({ 4 loadAllTodos: rxMethod( 5 switchMap(() => { 6 patchState(store, {loading: true}); 7 8 return todoService.getItems().pipe( 9 10 11 12 13 14 ); 15 }) 16 ), 17 })) 18 ); 19
  38. export const TodoStore = signalStore( { providedIn: 'root' }, withState({

    /* state goes here */ ), withMethods((store, todoService = inject(TodoService)) => ({ loadAllTodos(store, todoService: TodoService) { ... }, async loadAllTodosByPromise() { patchState(store, { loading: true }); const items = await todoService.getItemsAsPromise(); patchState(store, { items, loading: false }); }, })) ); 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15
  39. export const TodoStore = signalStore( { providedIn: 'root' }, withState({

    /* state goes here */ ), withMethods((store, todoService = inject(TodoService)) => ({ loadAllTodos(store, todoService: TodoService) { ... }, async loadAllTodosByPromise() { patchState(store, { loading: true }); const items = await todoService.getItemsAsPromise(); patchState(store, { items, loading: false }); }, })) ); 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 async loadAllTodosByPromise() { patchState(store, { loading: true }); const items = await todoService.getItemsAsPromise(); patchState(store, { items, loading: false }); }, export const TodoStore = signalStore( 1 { providedIn: 'root' }, 2 withState({ /* state goes here */ ), 3 withMethods((store, todoService = inject(TodoService)) => ({ 4 loadAllTodos(store, todoService: TodoService) { ... }, 5 6 7 8 9 10 11 12 13 })) 14 ); 15
  40. @Component({ // ... providers: [TodoStore], }) export class AppComponent implements

    OnInit { readonly store = inject(TodoStore); ngOnInit() { this.store.loadAllTodos(); } } 1 2 3 4 5 6 7 8 9 10 11
  41. @Component({ // ... providers: [TodoStore], }) export class AppComponent implements

    OnInit { readonly store = inject(TodoStore); ngOnInit() { this.store.loadAllTodos(); } } 1 2 3 4 5 6 7 8 9 10 11 providers: [TodoStore], @Component({ 1 // ... 2 3 }) 4 export class AppComponent implements OnInit { 5 readonly store = inject(TodoStore); 6 7 ngOnInit() { 8 this.store.loadAllTodos(); 9 } 10 } 11
  42. @Component({ // ... providers: [TodoStore], }) export class AppComponent implements

    OnInit { readonly store = inject(TodoStore); ngOnInit() { this.store.loadAllTodos(); } } 1 2 3 4 5 6 7 8 9 10 11 providers: [TodoStore], @Component({ 1 // ... 2 3 }) 4 export class AppComponent implements OnInit { 5 readonly store = inject(TodoStore); 6 7 ngOnInit() { 8 this.store.loadAllTodos(); 9 } 10 } 11 readonly store = inject(TodoStore); @Component({ 1 // ... 2 providers: [TodoStore], 3 }) 4 export class AppComponent implements OnInit { 5 6 7 ngOnInit() { 8 this.store.loadAllTodos(); 9 } 10 } 11
  43. @Component({ // ... providers: [TodoStore], }) export class AppComponent implements

    OnInit { readonly store = inject(TodoStore); ngOnInit() { this.store.loadAllTodos(); } } 1 2 3 4 5 6 7 8 9 10 11 providers: [TodoStore], @Component({ 1 // ... 2 3 }) 4 export class AppComponent implements OnInit { 5 readonly store = inject(TodoStore); 6 7 ngOnInit() { 8 this.store.loadAllTodos(); 9 } 10 } 11 readonly store = inject(TodoStore); @Component({ 1 // ... 2 providers: [TodoStore], 3 }) 4 export class AppComponent implements OnInit { 5 6 7 ngOnInit() { 8 this.store.loadAllTodos(); 9 } 10 } 11 this.store.loadAllTodos(); @Component({ 1 // ... 2 providers: [TodoStore], 3 }) 4 export class AppComponent implements OnInit { 5 readonly store = inject(TodoStore); 6 7 ngOnInit() { 8 9 } 10 } 11
  44. template: `{{ store.items() }} {{ store.loading() }}`, readonly store =

    inject(TodoStore); @Component({ 1 // ... 2 3 providers: [TodoStore], 4 }) 5 export class AppComponent implements OnInit { 6 7 8 ngOnInit() { 9 this.store.loadAllTodos(); 10 } 11 } 12
  45. export const TodoStore = signalStore( { providedIn: 'root' }, withState(initialState),

    withMethods(/* ... */), withHooks({ // Hooks }) ); 1 2 3 4 5 6 7 8
  46. export const TodoStore = signalStore( { providedIn: 'root' }, withState(initialState),

    withMethods(/* ... */), withHooks({ // Hooks }) ); 1 2 3 4 5 6 7 8 withHooks({ // Hooks }) ); export const TodoStore = signalStore( 1 { providedIn: 'root' }, 2 withState(initialState), 3 withMethods(/* ... */), 4 5 6 7 8
  47. export const TodoStore = signalStore( { providedIn: 'root' }, withState(initialState),

    withMethods(/* ... */), withHooks({ onInit({ loadAllTodos }) { loadAllTodos(); }, onDestroy() { console.log('on destroy'); }, }) ); 1 2 3 4 5 6 7 8 9 10 11 12 13
  48. export const TodoStore = signalStore( { providedIn: 'root' }, withState(initialState),

    withMethods(/* ... */), withHooks({ onInit({ loadAllTodos }) { loadAllTodos(); }, onDestroy() { console.log('on destroy'); }, }) ); 1 2 3 4 5 6 7 8 9 10 11 12 13 withHooks({ onInit({ loadAllTodos }) { loadAllTodos(); }, onDestroy() { console.log('on destroy'); }, }) export const TodoStore = signalStore( 1 { providedIn: 'root' }, 2 withState(initialState), 3 withMethods(/* ... */), 4 5 6 7 8 9 10 11 12 ); 13
  49. @Component({ // ... providers: [TodoStore], }) export class AppComponent implements

    OnInit { readonly store = inject(TodoStore); ngOnInit() { this.store.loadAllTodos(); } } 1 2 3 4 5 6 7 8 9 10 11
  50. @Component({ // ... providers: [TodoStore], }) export class AppComponent {

    readonly store = inject(TodoStore); } 1 2 3 4 5 6 7
  51. withComputed((state) => ({ /* ... */ })), export const TodoStore

    = signalStore( 1 withState(initialState), 2 3 4 5 6 7 withMethods(/* ... */), 8 withHooks(/* ... */) 9 ); 10
  52. export const TodoStore = signalStore( withState(initialState), withComputed(({ items }) =>

    ({ doneCount: computed(() => items().filter((x) => x.done).length), undoneCount: computed(() => items().filter((x) => !x.done).length), percentageDone: computed(() => { const done = items().filter((x) => x.done).length; const total = items().length; if (total === 0) { return 0; } return (done / total) * 100; }), })), withMethods(/* ... */), withHooks(/* ... */) ); 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21
  53. export const TodoStore = signalStore( withState(initialState), withComputed(({ items }) =>

    ({ doneCount: computed(() => items().filter((x) => x.done).length), undoneCount: computed(() => items().filter((x) => !x.done).length), percentageDone: computed(() => { const done = items().filter((x) => x.done).length; const total = items().length; if (total === 0) { return 0; } return (done / total) * 100; }), })), withMethods(/* ... */), withHooks(/* ... */) ); 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 withComputed(({ items }) => ({ doneCount: computed(() => items().filter((x) => x.done).length), undoneCount: computed(() => items().filter((x) => !x.done).length), percentageDone: computed(() => { const done = items().filter((x) => x.done).length; const total = items().length; if (total === 0) { return 0; } return (done / total) * 100; }), })), export const TodoStore = signalStore( 1 withState(initialState), 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 withMethods(/* ... */), 19 withHooks(/* ... */) 20 ); 21
  54. export const TodoStore = signalStore( withState(initialState), withComputed(({ items }) =>

    ({ doneCount: computed(() => items().filter((x) => x.done).length), undoneCount: computed(() => items().filter((x) => !x.done).length), percentageDone: computed(() => { const done = items().filter((x) => x.done).length; const total = items().length; if (total === 0) { return 0; } return (done / total) * 100; }), })), withMethods(/* ... */), withHooks(/* ... */) ); 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 withComputed(({ items }) => ({ doneCount: computed(() => items().filter((x) => x.done).length), undoneCount: computed(() => items().filter((x) => !x.done).length), percentageDone: computed(() => { const done = items().filter((x) => x.done).length; const total = items().length; if (total === 0) { return 0; } return (done / total) * 100; }), })), export const TodoStore = signalStore( 1 withState(initialState), 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 withMethods(/* ... */), 19 withHooks(/* ... */) 20 ); 21 doneCount: computed(() => items().filter((x) => x.done).length), export const TodoStore = signalStore( 1 withState(initialState), 2 3 withComputed(({ items }) => ({ 4 5 undoneCount: computed(() => items().filter((x) => !x.done).length), 6 percentageDone: computed(() => { 7 const done = items().filter((x) => x.done).length; 8 const total = items().length; 9 10 if (total === 0) { 11 return 0; 12 } 13 14 return (done / total) * 100; 15 }), 16 })), 17 18 withMethods(/* ... */), 19 withHooks(/* ... */) 20 ); 21
  55. export const TodoStore = signalStore( withState(initialState), withComputed(({ items }) =>

    ({ doneCount: computed(() => items().filter((x) => x.done).length), undoneCount: computed(() => items().filter((x) => !x.done).length), percentageDone: computed(() => { const done = items().filter((x) => x.done).length; const total = items().length; if (total === 0) { return 0; } return (done / total) * 100; }), })), withMethods(/* ... */), withHooks(/* ... */) ); 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 withComputed(({ items }) => ({ doneCount: computed(() => items().filter((x) => x.done).length), undoneCount: computed(() => items().filter((x) => !x.done).length), percentageDone: computed(() => { const done = items().filter((x) => x.done).length; const total = items().length; if (total === 0) { return 0; } return (done / total) * 100; }), })), export const TodoStore = signalStore( 1 withState(initialState), 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 withMethods(/* ... */), 19 withHooks(/* ... */) 20 ); 21 doneCount: computed(() => items().filter((x) => x.done).length), export const TodoStore = signalStore( 1 withState(initialState), 2 3 withComputed(({ items }) => ({ 4 5 undoneCount: computed(() => items().filter((x) => !x.done).length), 6 percentageDone: computed(() => { 7 const done = items().filter((x) => x.done).length; 8 const total = items().length; 9 10 if (total === 0) { 11 return 0; 12 } 13 14 return (done / total) * 100; 15 }), 16 })), 17 18 withMethods(/* ... */), 19 withHooks(/* ... */) 20 ); 21 undoneCount: computed(() => items().filter((x) => !x.done).length), export const TodoStore = signalStore( 1 withState(initialState), 2 3 withComputed(({ items }) => ({ 4 doneCount: computed(() => items().filter((x) => x.done).length), 5 6 percentageDone: computed(() => { 7 const done = items().filter((x) => x.done).length; 8 const total = items().length; 9 10 if (total === 0) { 11 return 0; 12 } 13 14 return (done / total) * 100; 15 }), 16 })), 17 18 withMethods(/* ... */), 19 withHooks(/* ... */) 20 ); 21
  56. export const TodoStore = signalStore( withState(initialState), withComputed(({ items }) =>

    ({ doneCount: computed(() => items().filter((x) => x.done).length), undoneCount: computed(() => items().filter((x) => !x.done).length), percentageDone: computed(() => { const done = items().filter((x) => x.done).length; const total = items().length; if (total === 0) { return 0; } return (done / total) * 100; }), })), withMethods(/* ... */), withHooks(/* ... */) ); 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 withComputed(({ items }) => ({ doneCount: computed(() => items().filter((x) => x.done).length), undoneCount: computed(() => items().filter((x) => !x.done).length), percentageDone: computed(() => { const done = items().filter((x) => x.done).length; const total = items().length; if (total === 0) { return 0; } return (done / total) * 100; }), })), export const TodoStore = signalStore( 1 withState(initialState), 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 withMethods(/* ... */), 19 withHooks(/* ... */) 20 ); 21 doneCount: computed(() => items().filter((x) => x.done).length), export const TodoStore = signalStore( 1 withState(initialState), 2 3 withComputed(({ items }) => ({ 4 5 undoneCount: computed(() => items().filter((x) => !x.done).length), 6 percentageDone: computed(() => { 7 const done = items().filter((x) => x.done).length; 8 const total = items().length; 9 10 if (total === 0) { 11 return 0; 12 } 13 14 return (done / total) * 100; 15 }), 16 })), 17 18 withMethods(/* ... */), 19 withHooks(/* ... */) 20 ); 21 undoneCount: computed(() => items().filter((x) => !x.done).length), export const TodoStore = signalStore( 1 withState(initialState), 2 3 withComputed(({ items }) => ({ 4 doneCount: computed(() => items().filter((x) => x.done).length), 5 6 percentageDone: computed(() => { 7 const done = items().filter((x) => x.done).length; 8 const total = items().length; 9 10 if (total === 0) { 11 return 0; 12 } 13 14 return (done / total) * 100; 15 }), 16 })), 17 18 withMethods(/* ... */), 19 withHooks(/* ... */) 20 ); 21 percentageDone: computed(() => { const done = items().filter((x) => x.done).length; const total = items().length; if (total === 0) { return 0; } return (done / total) * 100; }), export const TodoStore = signalStore( 1 withState(initialState), 2 3 withComputed(({ items }) => ({ 4 doneCount: computed(() => items().filter((x) => x.done).length), 5 undoneCount: computed(() => items().filter((x) => !x.done).length), 6 7 8 9 10 11 12 13 14 15 16 })), 17 18 withMethods(/* ... */), 19 withHooks(/* ... */) 20 ); 21
  57. @Component({ providers: [TodoStore], templates: ` <div> {{ store.doneCount() }} /

    {{ store.undoneCount() }} {{ store.percentageDone() }} </div> ` }) export class AppComponent implements OnInit { readonly store = inject(TodoStore); } 1 2 3 4 5 6 7 8 9 10 11
  58. @Component({ providers: [TodoStore], templates: ` <div> {{ store.doneCount() }} /

    {{ store.undoneCount() }} {{ store.percentageDone() }} </div> ` }) export class AppComponent implements OnInit { readonly store = inject(TodoStore); } 1 2 3 4 5 6 7 8 9 10 11 <div> {{ store.doneCount() }} / {{ store.undoneCount() }} {{ store.percentageDone() }} </div> ` @Component({ 1 providers: [TodoStore], 2 templates: ` 3 4 5 6 7 }) 8 export class AppComponent implements OnInit { 9 readonly store = inject(TodoStore); 10 } 11
  59. import { computed } from '@angular/core'; import { signalStoreFeature, withComputed,

    withState } from '@ngrx/signals'; export type RequestStatus = 'idle' | 'pending' | 'fulfilled' | { error: string }; export type RequestStatusState = { requestStatus: RequestStatus }; export function withRequestStatus() { return signalStoreFeature( withState<RequestStatusState>({ requestStatus: 'idle' }), withComputed(({ requestStatus }) => ({ isPending: computed(() => requestStatus() === 'pending'), isFulfilled: computed(() => requestStatus() === 'fulfilled'), error: computed(() => { const status = requestStatus(); return typeof status === 'object' ? status.error : null; }), })) ); } 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22
  60. import { computed } from '@angular/core'; import { signalStoreFeature, withComputed,

    withState } from '@ngrx/signals'; export type RequestStatus = 'idle' | 'pending' | 'fulfilled' | { error: string }; export type RequestStatusState = { requestStatus: RequestStatus }; export function withRequestStatus() { return signalStoreFeature( withState<RequestStatusState>({ requestStatus: 'idle' }), withComputed(({ requestStatus }) => ({ isPending: computed(() => requestStatus() === 'pending'), isFulfilled: computed(() => requestStatus() === 'fulfilled'), error: computed(() => { const status = requestStatus(); return typeof status === 'object' ? status.error : null; }), })) ); } 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 export function withRequestStatus() { import { computed } from '@angular/core'; 1 import { signalStoreFeature, withComputed, withState } from '@ngrx/signals'; 2 3 export type RequestStatus = 'idle' | 'pending' | 'fulfilled' | { error: string }; 4 export type RequestStatusState = { requestStatus: RequestStatus }; 5 6 7 return signalStoreFeature( 8 withState<RequestStatusState>({ requestStatus: 'idle' }), 9 withComputed(({ requestStatus }) => ({ 10 isPending: computed(() => requestStatus() === 'pending'), 11 12 isFulfilled: computed(() => requestStatus() === 'fulfilled'), 13 14 error: computed(() => { 15 const status = requestStatus(); 16 17 return typeof status === 'object' ? status.error : null; 18 }), 19 })) 20 ); 21 } 22
  61. import { computed } from '@angular/core'; import { signalStoreFeature, withComputed,

    withState } from '@ngrx/signals'; export type RequestStatus = 'idle' | 'pending' | 'fulfilled' | { error: string }; export type RequestStatusState = { requestStatus: RequestStatus }; export function withRequestStatus() { return signalStoreFeature( withState<RequestStatusState>({ requestStatus: 'idle' }), withComputed(({ requestStatus }) => ({ isPending: computed(() => requestStatus() === 'pending'), isFulfilled: computed(() => requestStatus() === 'fulfilled'), error: computed(() => { const status = requestStatus(); return typeof status === 'object' ? status.error : null; }), })) ); } 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 export function withRequestStatus() { import { computed } from '@angular/core'; 1 import { signalStoreFeature, withComputed, withState } from '@ngrx/signals'; 2 3 export type RequestStatus = 'idle' | 'pending' | 'fulfilled' | { error: string }; 4 export type RequestStatusState = { requestStatus: RequestStatus }; 5 6 7 return signalStoreFeature( 8 withState<RequestStatusState>({ requestStatus: 'idle' }), 9 withComputed(({ requestStatus }) => ({ 10 isPending: computed(() => requestStatus() === 'pending'), 11 12 isFulfilled: computed(() => requestStatus() === 'fulfilled'), 13 14 error: computed(() => { 15 const status = requestStatus(); 16 17 return typeof status === 'object' ? status.error : null; 18 }), 19 })) 20 ); 21 } 22 return signalStoreFeature( import { computed } from '@angular/core'; 1 import { signalStoreFeature, withComputed, withState } from '@ngrx/signals'; 2 3 export type RequestStatus = 'idle' | 'pending' | 'fulfilled' | { error: string }; 4 export type RequestStatusState = { requestStatus: RequestStatus }; 5 6 export function withRequestStatus() { 7 8 withState<RequestStatusState>({ requestStatus: 'idle' }), 9 withComputed(({ requestStatus }) => ({ 10 isPending: computed(() => requestStatus() === 'pending'), 11 12 isFulfilled: computed(() => requestStatus() === 'fulfilled'), 13 14 error: computed(() => { 15 const status = requestStatus(); 16 17 return typeof status === 'object' ? status.error : null; 18 }), 19 })) 20 ); 21 } 22
  62. import { computed } from '@angular/core'; import { signalStoreFeature, withComputed,

    withState } from '@ngrx/signals'; export type RequestStatus = 'idle' | 'pending' | 'fulfilled' | { error: string }; export type RequestStatusState = { requestStatus: RequestStatus }; export function withRequestStatus() { return signalStoreFeature( withState<RequestStatusState>({ requestStatus: 'idle' }), withComputed(({ requestStatus }) => ({ isPending: computed(() => requestStatus() === 'pending'), isFulfilled: computed(() => requestStatus() === 'fulfilled'), error: computed(() => { const status = requestStatus(); return typeof status === 'object' ? status.error : null; }), })) ); } 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 export function withRequestStatus() { import { computed } from '@angular/core'; 1 import { signalStoreFeature, withComputed, withState } from '@ngrx/signals'; 2 3 export type RequestStatus = 'idle' | 'pending' | 'fulfilled' | { error: string }; 4 export type RequestStatusState = { requestStatus: RequestStatus }; 5 6 7 return signalStoreFeature( 8 withState<RequestStatusState>({ requestStatus: 'idle' }), 9 withComputed(({ requestStatus }) => ({ 10 isPending: computed(() => requestStatus() === 'pending'), 11 12 isFulfilled: computed(() => requestStatus() === 'fulfilled'), 13 14 error: computed(() => { 15 const status = requestStatus(); 16 17 return typeof status === 'object' ? status.error : null; 18 }), 19 })) 20 ); 21 } 22 return signalStoreFeature( import { computed } from '@angular/core'; 1 import { signalStoreFeature, withComputed, withState } from '@ngrx/signals'; 2 3 export type RequestStatus = 'idle' | 'pending' | 'fulfilled' | { error: string }; 4 export type RequestStatusState = { requestStatus: RequestStatus }; 5 6 export function withRequestStatus() { 7 8 withState<RequestStatusState>({ requestStatus: 'idle' }), 9 withComputed(({ requestStatus }) => ({ 10 isPending: computed(() => requestStatus() === 'pending'), 11 12 isFulfilled: computed(() => requestStatus() === 'fulfilled'), 13 14 error: computed(() => { 15 const status = requestStatus(); 16 17 return typeof status === 'object' ? status.error : null; 18 }), 19 })) 20 ); 21 } 22 withState<RequestStatusState>({ requestStatus: 'idle' }), import { computed } from '@angular/core'; 1 import { signalStoreFeature, withComputed, withState } from '@ngrx/signals'; 2 3 export type RequestStatus = 'idle' | 'pending' | 'fulfilled' | { error: string }; 4 export type RequestStatusState = { requestStatus: RequestStatus }; 5 6 export function withRequestStatus() { 7 return signalStoreFeature( 8 9 withComputed(({ requestStatus }) => ({ 10 isPending: computed(() => requestStatus() === 'pending'), 11 12 isFulfilled: computed(() => requestStatus() === 'fulfilled'), 13 14 error: computed(() => { 15 const status = requestStatus(); 16 17 return typeof status === 'object' ? status.error : null; 18 }), 19 })) 20 ); 21 } 22
  63. import { computed } from '@angular/core'; import { signalStoreFeature, withComputed,

    withState } from '@ngrx/signals'; export type RequestStatus = 'idle' | 'pending' | 'fulfilled' | { error: string }; export type RequestStatusState = { requestStatus: RequestStatus }; export function withRequestStatus() { return signalStoreFeature( withState<RequestStatusState>({ requestStatus: 'idle' }), withComputed(({ requestStatus }) => ({ isPending: computed(() => requestStatus() === 'pending'), isFulfilled: computed(() => requestStatus() === 'fulfilled'), error: computed(() => { const status = requestStatus(); return typeof status === 'object' ? status.error : null; }), })) ); } 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 export function withRequestStatus() { import { computed } from '@angular/core'; 1 import { signalStoreFeature, withComputed, withState } from '@ngrx/signals'; 2 3 export type RequestStatus = 'idle' | 'pending' | 'fulfilled' | { error: string }; 4 export type RequestStatusState = { requestStatus: RequestStatus }; 5 6 7 return signalStoreFeature( 8 withState<RequestStatusState>({ requestStatus: 'idle' }), 9 withComputed(({ requestStatus }) => ({ 10 isPending: computed(() => requestStatus() === 'pending'), 11 12 isFulfilled: computed(() => requestStatus() === 'fulfilled'), 13 14 error: computed(() => { 15 const status = requestStatus(); 16 17 return typeof status === 'object' ? status.error : null; 18 }), 19 })) 20 ); 21 } 22 return signalStoreFeature( import { computed } from '@angular/core'; 1 import { signalStoreFeature, withComputed, withState } from '@ngrx/signals'; 2 3 export type RequestStatus = 'idle' | 'pending' | 'fulfilled' | { error: string }; 4 export type RequestStatusState = { requestStatus: RequestStatus }; 5 6 export function withRequestStatus() { 7 8 withState<RequestStatusState>({ requestStatus: 'idle' }), 9 withComputed(({ requestStatus }) => ({ 10 isPending: computed(() => requestStatus() === 'pending'), 11 12 isFulfilled: computed(() => requestStatus() === 'fulfilled'), 13 14 error: computed(() => { 15 const status = requestStatus(); 16 17 return typeof status === 'object' ? status.error : null; 18 }), 19 })) 20 ); 21 } 22 withState<RequestStatusState>({ requestStatus: 'idle' }), import { computed } from '@angular/core'; 1 import { signalStoreFeature, withComputed, withState } from '@ngrx/signals'; 2 3 export type RequestStatus = 'idle' | 'pending' | 'fulfilled' | { error: string }; 4 export type RequestStatusState = { requestStatus: RequestStatus }; 5 6 export function withRequestStatus() { 7 return signalStoreFeature( 8 9 withComputed(({ requestStatus }) => ({ 10 isPending: computed(() => requestStatus() === 'pending'), 11 12 isFulfilled: computed(() => requestStatus() === 'fulfilled'), 13 14 error: computed(() => { 15 const status = requestStatus(); 16 17 return typeof status === 'object' ? status.error : null; 18 }), 19 })) 20 ); 21 } 22 withComputed(({ requestStatus }) => ({ import { computed } from '@angular/core'; 1 import { signalStoreFeature, withComputed, withState } from '@ngrx/signals'; 2 3 export type RequestStatus = 'idle' | 'pending' | 'fulfilled' | { error: string }; 4 export type RequestStatusState = { requestStatus: RequestStatus }; 5 6 export function withRequestStatus() { 7 return signalStoreFeature( 8 withState<RequestStatusState>({ requestStatus: 'idle' }), 9 10 isPending: computed(() => requestStatus() === 'pending'), 11 12 isFulfilled: computed(() => requestStatus() === 'fulfilled'), 13 14 error: computed(() => { 15 const status = requestStatus(); 16 17 return typeof status === 'object' ? status.error : null; 18 }), 19 })) 20 ); 21 } 22
  64. import { computed } from '@angular/core'; import { signalStoreFeature, withComputed,

    withState } from '@ngrx/signals'; export type RequestStatus = 'idle' | 'pending' | 'fulfilled' | { error: string }; export type RequestStatusState = { requestStatus: RequestStatus }; export function withRequestStatus() { return signalStoreFeature( withState<RequestStatusState>({ requestStatus: 'idle' }), withComputed(({ requestStatus }) => ({ isPending: computed(() => requestStatus() === 'pending'), isFulfilled: computed(() => requestStatus() === 'fulfilled'), error: computed(() => { const status = requestStatus(); return typeof status === 'object' ? status.error : null; }), })) ); } 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 export function withRequestStatus() { import { computed } from '@angular/core'; 1 import { signalStoreFeature, withComputed, withState } from '@ngrx/signals'; 2 3 export type RequestStatus = 'idle' | 'pending' | 'fulfilled' | { error: string }; 4 export type RequestStatusState = { requestStatus: RequestStatus }; 5 6 7 return signalStoreFeature( 8 withState<RequestStatusState>({ requestStatus: 'idle' }), 9 withComputed(({ requestStatus }) => ({ 10 isPending: computed(() => requestStatus() === 'pending'), 11 12 isFulfilled: computed(() => requestStatus() === 'fulfilled'), 13 14 error: computed(() => { 15 const status = requestStatus(); 16 17 return typeof status === 'object' ? status.error : null; 18 }), 19 })) 20 ); 21 } 22 return signalStoreFeature( import { computed } from '@angular/core'; 1 import { signalStoreFeature, withComputed, withState } from '@ngrx/signals'; 2 3 export type RequestStatus = 'idle' | 'pending' | 'fulfilled' | { error: string }; 4 export type RequestStatusState = { requestStatus: RequestStatus }; 5 6 export function withRequestStatus() { 7 8 withState<RequestStatusState>({ requestStatus: 'idle' }), 9 withComputed(({ requestStatus }) => ({ 10 isPending: computed(() => requestStatus() === 'pending'), 11 12 isFulfilled: computed(() => requestStatus() === 'fulfilled'), 13 14 error: computed(() => { 15 const status = requestStatus(); 16 17 return typeof status === 'object' ? status.error : null; 18 }), 19 })) 20 ); 21 } 22 withState<RequestStatusState>({ requestStatus: 'idle' }), import { computed } from '@angular/core'; 1 import { signalStoreFeature, withComputed, withState } from '@ngrx/signals'; 2 3 export type RequestStatus = 'idle' | 'pending' | 'fulfilled' | { error: string }; 4 export type RequestStatusState = { requestStatus: RequestStatus }; 5 6 export function withRequestStatus() { 7 return signalStoreFeature( 8 9 withComputed(({ requestStatus }) => ({ 10 isPending: computed(() => requestStatus() === 'pending'), 11 12 isFulfilled: computed(() => requestStatus() === 'fulfilled'), 13 14 error: computed(() => { 15 const status = requestStatus(); 16 17 return typeof status === 'object' ? status.error : null; 18 }), 19 })) 20 ); 21 } 22 withComputed(({ requestStatus }) => ({ import { computed } from '@angular/core'; 1 import { signalStoreFeature, withComputed, withState } from '@ngrx/signals'; 2 3 export type RequestStatus = 'idle' | 'pending' | 'fulfilled' | { error: string }; 4 export type RequestStatusState = { requestStatus: RequestStatus }; 5 6 export function withRequestStatus() { 7 return signalStoreFeature( 8 withState<RequestStatusState>({ requestStatus: 'idle' }), 9 10 isPending: computed(() => requestStatus() === 'pending'), 11 12 isFulfilled: computed(() => requestStatus() === 'fulfilled'), 13 14 error: computed(() => { 15 const status = requestStatus(); 16 17 return typeof status === 'object' ? status.error : null; 18 }), 19 })) 20 ); 21 } 22 isPending: computed(() => requestStatus() === 'pending'), isFulfilled: computed(() => requestStatus() === 'fulfilled'), error: computed(() => { const status = requestStatus(); return typeof status === 'object' ? status.error : null; }), import { computed } from '@angular/core'; 1 import { signalStoreFeature, withComputed, withState } from '@ngrx/signals'; 2 3 export type RequestStatus = 'idle' | 'pending' | 'fulfilled' | { error: string }; 4 export type RequestStatusState = { requestStatus: RequestStatus }; 5 6 export function withRequestStatus() { 7 return signalStoreFeature( 8 withState<RequestStatusState>({ requestStatus: 'idle' }), 9 withComputed(({ requestStatus }) => ({ 10 11 12 13 14 15 16 17 18 19 })) 20 ); 21 } 22
  65. export function setPending(): RequestStatusState { return { requestStatus: 'pending' };

    } export function setFulfilled(): RequestStatusState { return { requestStatus: 'fulfilled' }; } export function setError(error: string): RequestStatusState { return { requestStatus: { error } }; } 1 2 3 4 5 6 7 8 9 10 11
  66. export const TodoStore = signalStore( withState(initialState), withRequestStatus(), withComputed(/* ... */),

    withMethods(/* ... */), withHooks(/* ... */) ); readonly store = inject(TodoStore); store.requestStatus(); store.isPending(); store.isFulfilled(); 1 2 3 4 5 6 7 8 9 10 11 12 13
  67. export const TodoStore = signalStore( withState(initialState), withRequestStatus(), withComputed(/* ... */),

    withMethods(/* ... */), withHooks(/* ... */) ); readonly store = inject(TodoStore); store.requestStatus(); store.isPending(); store.isFulfilled(); 1 2 3 4 5 6 7 8 9 10 11 12 13 withRequestStatus(), export const TodoStore = signalStore( 1 withState(initialState), 2 3 withComputed(/* ... */), 4 withMethods(/* ... */), 5 withHooks(/* ... */) 6 ); 7 8 readonly store = inject(TodoStore); 9 10 store.requestStatus(); 11 store.isPending(); 12 store.isFulfilled(); 13
  68. export const TodoStore = signalStore( withState(initialState), withRequestStatus(), withComputed(/* ... */),

    withMethods(/* ... */), withHooks(/* ... */) ); readonly store = inject(TodoStore); store.requestStatus(); store.isPending(); store.isFulfilled(); 1 2 3 4 5 6 7 8 9 10 11 12 13 withRequestStatus(), export const TodoStore = signalStore( 1 withState(initialState), 2 3 withComputed(/* ... */), 4 withMethods(/* ... */), 5 withHooks(/* ... */) 6 ); 7 8 readonly store = inject(TodoStore); 9 10 store.requestStatus(); 11 store.isPending(); 12 store.isFulfilled(); 13 readonly store = inject(TodoStore); export const TodoStore = signalStore( 1 withState(initialState), 2 withRequestStatus(), 3 withComputed(/* ... */), 4 withMethods(/* ... */), 5 withHooks(/* ... */) 6 ); 7 8 9 10 store.requestStatus(); 11 store.isPending(); 12 store.isFulfilled(); 13
  69. export const TodoStore = signalStore( withState(initialState), withRequestStatus(), withComputed(/* ... */),

    withMethods(/* ... */), withHooks(/* ... */) ); readonly store = inject(TodoStore); store.requestStatus(); store.isPending(); store.isFulfilled(); 1 2 3 4 5 6 7 8 9 10 11 12 13 withRequestStatus(), export const TodoStore = signalStore( 1 withState(initialState), 2 3 withComputed(/* ... */), 4 withMethods(/* ... */), 5 withHooks(/* ... */) 6 ); 7 8 readonly store = inject(TodoStore); 9 10 store.requestStatus(); 11 store.isPending(); 12 store.isFulfilled(); 13 readonly store = inject(TodoStore); export const TodoStore = signalStore( 1 withState(initialState), 2 withRequestStatus(), 3 withComputed(/* ... */), 4 withMethods(/* ... */), 5 withHooks(/* ... */) 6 ); 7 8 9 10 store.requestStatus(); 11 store.isPending(); 12 store.isFulfilled(); 13 store.requestStatus(); store.isPending(); store.isFulfilled(); export const TodoStore = signalStore( 1 withState(initialState), 2 withRequestStatus(), 3 withComputed(/* ... */), 4 withMethods(/* ... */), 5 withHooks(/* ... */) 6 ); 7 8 readonly store = inject(TodoStore); 9 10 11 12 13
  70. export const ProductsActions = createActionGroup({ source: '[Products]', events: { 'Load

    Products': emptyProps(), 'Load Products Success': props<{ products: Product[] }>(), 'Load Products Failure': props<{ error: HttpErrorResponse }>(), 'Navigate To Detail': props<{ id: string }>(), }, });
  71. import { emptyProps, eventGroup, props } from '@ngrx/signals/events'; export const

    usersPageEvents = eventGroup({ source: 'Users Page', events: { opened: emptyProps(), refreshed: emptyProps(), }, }); export const usersApiEvents = eventGroup({ source: 'Users API', events: { usersLoadedSuccess: props<{ users: User[] }>(), usersLoadedFailure: props<{ error: string }>(), }, });
  72. import { when, withReducer } from '@ngrx/signals/events'; export const UsersStore

    = signalStore( { providedIn: 'root' }, withEntities<User>(), withRequestStatus(), withReducer( when(usersPageEvents.opened, usersPageEvents.refreshed, setPending), when(usersApiEvents.usersLoadedSuccess, ({ users }) => [ setAllEntities(users), setFulfilled(), ]), when(usersApiEvents.usersLoadedError, ({ error }) => setError(error)), ), );
  73. export const loadProducts$ = createEffect( (actions$ = inject(Actions), productsService =

    inject(ProductsService)) => actions$.pipe( ofType(ProductsActions.loadProducts), exhaustMap(() => productsService.loadProducts().pipe( map((products) => ProductsActions.loadProductsSuccess({ products })), catchError((error: HttpErrorResponse) => of(ProductsActions.loadProductsFailure({ error })), ), ), ), ), { functional: true }, );
  74. export const UsersStore = signalStore( /* ... */ withEffects( (_,

    events = inject(Events), usersService = inject(UsersService),) => ({ loadUsers$: events .on(usersPageEvents.opened, usersPageEvents.refreshed) .pipe( exhaustMap(() => usersService.getAll().pipe( mapResponse({ next: (users) => usersApiEvents.usersLoadedSuccess({ users }), error: (error: { message: string }) => usersApiEvents.usersLoadedError({ error: error.message }), }), ), ), ), logError$: events .on(usersApiEvents.usersLoadedError) .pipe(tap(({ error }) => console.log(error))), }), ), );
  75. @Component({ template: `<div class="products-list"> @for (productCategory of productsByCategories(); track productCategory.category

    ) { ... }</div> ` }) export class ProductsComponent implements OnInit { private readonly store = inject(Store); readonly productsByCategories = this.store.selectSignal( selectProductsByCategories, ); }
  76. @Component({ selector: 'app-users', template: ` <h1>Users</h1> @if (usersStore.isPending()) { <p>Loading...</p>

    } <ul> @for (user of usersStore.entities(); track user.id) { <li>{{ user.name }}</li> } </ul> `, }) export class UsersComponent { readonly usersStore = inject(UsersStore); }
  77. @Component({ template: `...` }) export class ProductsComponent implements OnInit {

    private readonly store = inject(Store); ngOnInit(): void { this.store.dispatch(ProductsActions.loadProducts()); } }
  78. @Component({ template: ` <h1>Users</h1> <button (click)="onRefresh()">Refresh</button> <!-- ... --> `,

    }) export class UsersComponent implements OnInit { readonly usersStore = inject(UsersStore); readonly dispatcher = inject(Dispatcher); ngOnInit() { this.dispatcher.dispatch(usersPageEvents.opened()); } onRefresh(): void { this.dispatcher.dispatch(usersPageEvents.refreshed()); } }
  79. signal store signal store events plugin classic store biggest and

    clearest separation actions, reducer, effects, store, ...
  80. signal store signal store events plugin classic store biggest and

    clearest separation actions, reducer, effects, store, ... each store as a service no separate actions, reducer, etc. extensible with custom features calling methods on store
  81. signal store signal store events plugin classic store biggest and

    clearest separation actions, reducer, effects, store, ... each store as a service no separate actions, reducer, etc. extensible with custom features actions, reducer, effects separated calling methods on store dispatching actions signal store w/ classic ngrx everything signal store has