Slide 1

Slide 1 text

mastering state management with the ngrx signal store

Slide 2

Slide 2 text

Hi, I am Fabian

Slide 3

Slide 3 text

@fabiangosebrink.bsky.social

Slide 4

Slide 4 text

you have to deal with state

Slide 5

Slide 5 text

signals

Slide 6

Slide 6 text

what are signals?

Slide 7

Slide 7 text

const todos = signal([]);

Slide 8

Slide 8 text

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

Slide 9

Slide 9 text

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

Slide 10

Slide 10 text

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

Slide 11

Slide 11 text

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

Slide 12

Slide 12 text

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

Slide 13

Slide 13 text

todos = signal([]); 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

Slide 14

Slide 14 text

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

Slide 15

Slide 15 text

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

Slide 16

Slide 16 text

todos = signal([]); 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([]); 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([]); 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([]); 1 2 count = computed(() => todos().length); 3 4 count = linkedSignal(() => todos.length); 5 6 7 8 9

Slide 17

Slide 17 text

but why?

Slide 18

Slide 18 text

optimized ui updates

Slide 19

Slide 19 text

zoneless

Slide 20

Slide 20 text

no subscribe

Slide 21

Slide 21 text

no subscribe no unsubscribe

Slide 22

Slide 22 text

rxjs interop

Slide 23

Slide 23 text

observables signals &&

Slide 24

Slide 24 text

ways to use signals

Slide 25

Slide 25 text

component

Slide 26

Slide 26 text

export class TodoMainComponent implements OnInit { private url = `${environment.apiUrl}todos`; private http = inject(HttpClient); private todos = signal([]); 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

Slide 27

Slide 27 text

export class TodoMainComponent implements OnInit { private url = `${environment.apiUrl}todos`; private http = inject(HttpClient); private todos = signal([]); 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([]); 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

Slide 28

Slide 28 text

export class TodoMainComponent implements OnInit { private url = `${environment.apiUrl}todos`; private http = inject(HttpClient); private todos = signal([]); 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([]); 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([]); 4 5 6 7 8 9 10 11 12 13 // Method logic with http... 14 } 15

Slide 29

Slide 29 text

service

Slide 30

Slide 30 text

service as abstraction

Slide 31

Slide 31 text

@Injectable({ providedIn: 'root' }) export class TodoService { private url = `${environment.apiUrl}todos`; private http = inject(HttpClient); private todos = signal([]); 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(this.url).subscribe((todos) => this.todos.set(todos)); } replaceItem() { this.todos.update((items) => [...items]); } addItem(value: string) { this.http.post(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

Slide 32

Slide 32 text

@Injectable({ providedIn: 'root' }) export class TodoService { private url = `${environment.apiUrl}todos`; private http = inject(HttpClient); private todos = signal([]); 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(this.url).subscribe((todos) => this.todos.set(todos)); } replaceItem() { this.todos.update((items) => [...items]); } addItem(value: string) { this.http.post(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([]); @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(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(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

Slide 33

Slide 33 text

@Injectable({ providedIn: 'root' }) export class TodoService { private url = `${environment.apiUrl}todos`; private http = inject(HttpClient); private todos = signal([]); 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(this.url).subscribe((todos) => this.todos.set(todos)); } replaceItem() { this.todos.update((items) => [...items]); } addItem(value: string) { this.http.post(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([]); @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(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(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([]); 5 6 7 8 9 10 11 getItems() { 12 this.http.get(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(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

Slide 34

Slide 34 text

@Injectable({ providedIn: 'root' }) export class TodoService { private url = `${environment.apiUrl}todos`; private http = inject(HttpClient); private todos = signal([]); 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(this.url).subscribe((todos) => this.todos.set(todos)); } replaceItem() { this.todos.update((items) => [...items]); } addItem(value: string) { this.http.post(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([]); @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(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(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([]); 5 6 7 8 9 10 11 getItems() { 12 this.http.get(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(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(this.url).subscribe((todos) => this.todos.set(todos)); } replaceItem() { this.todos.update((items) => [...items]); } addItem(value: string) { this.http.post(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([]); 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

Slide 35

Slide 35 text

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

Slide 36

Slide 36 text

No content

Slide 37

Slide 37 text

No content

Slide 38

Slide 38 text

store

Slide 39

Slide 39 text

store as abstraction

Slide 40

Slide 40 text

store ui logic

Slide 41

Slide 41 text

store ui logic business logic, events, rxjs, websockets, ... computed, (a)sync glue signals, methods

Slide 42

Slide 42 text

@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

Slide 43

Slide 43 text

@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

Slide 44

Slide 44 text

@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

Slide 45

Slide 45 text

@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

Slide 46

Slide 46 text

@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 1 2 3

Slide 47

Slide 47 text

@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 1 2 3

Slide 48

Slide 48 text

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 1 2 3

Slide 49

Slide 49 text

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 1 2 3

Slide 50

Slide 50 text

No content

Slide 51

Slide 51 text

partly signals

Slide 52

Slide 52 text

partly rxjs partly signals

Slide 53

Slide 53 text

No content

Slide 54

Slide 54 text

No content

Slide 55

Slide 55 text

mastering state management with the ngrx signal store

Slide 56

Slide 56 text

No content

Slide 57

Slide 57 text

lightweight

Slide 58

Slide 58 text

functional based

Slide 59

Slide 59 text

highly extensible

Slide 60

Slide 60 text

optional rxjs

Slide 61

Slide 61 text

export const TodoStore = signalStore( ); 1 2 3 4 5 6 7 8 9 10 11 12 13

Slide 62

Slide 62 text

export const TodoStore = signalStore( withState(...), ); 1 2 3 4 5 6 7 8 9 10 11 12 13

Slide 63

Slide 63 text

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

Slide 64

Slide 64 text

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

Slide 65

Slide 65 text

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

Slide 66

Slide 66 text

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

Slide 67

Slide 67 text

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

Slide 68

Slide 68 text

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

Slide 69

Slide 69 text

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

Slide 70

Slide 70 text

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

Slide 71

Slide 71 text

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

Slide 72

Slide 72 text

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

Slide 73

Slide 73 text

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

Slide 74

Slide 74 text

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

Slide 75

Slide 75 text

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

Slide 76

Slide 76 text

optional rxjs

Slide 77

Slide 77 text

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

Slide 78

Slide 78 text

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

Slide 79

Slide 79 text

@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

Slide 80

Slide 80 text

@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

Slide 81

Slide 81 text

@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

Slide 82

Slide 82 text

@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

Slide 83

Slide 83 text

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

Slide 84

Slide 84 text

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

Slide 85

Slide 85 text

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

Slide 86

Slide 86 text

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

Slide 87

Slide 87 text

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

Slide 88

Slide 88 text

@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

Slide 89

Slide 89 text

@Component({ // ... providers: [TodoStore], }) export class AppComponent { readonly store = inject(TodoStore); } 1 2 3 4 5 6 7

Slide 90

Slide 90 text

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

Slide 91

Slide 91 text

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

Slide 92

Slide 92 text

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

Slide 93

Slide 93 text

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

Slide 94

Slide 94 text

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

Slide 95

Slide 95 text

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

Slide 96

Slide 96 text

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

Slide 97

Slide 97 text

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

Slide 98

Slide 98 text

No content

Slide 99

Slide 99 text

No content

Slide 100

Slide 100 text

custom features

Slide 101

Slide 101 text

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({ 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

Slide 102

Slide 102 text

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({ 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({ 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

Slide 103

Slide 103 text

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({ 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({ 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({ 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

Slide 104

Slide 104 text

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({ 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({ 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({ 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({ 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

Slide 105

Slide 105 text

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({ 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({ 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({ 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({ 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({ 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

Slide 106

Slide 106 text

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({ 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({ 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({ 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({ 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({ 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({ requestStatus: 'idle' }), 9 withComputed(({ requestStatus }) => ({ 10 11 12 13 14 15 16 17 18 19 })) 20 ); 21 } 22

Slide 107

Slide 107 text

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

Slide 108

Slide 108 text

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

Slide 109

Slide 109 text

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

Slide 110

Slide 110 text

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

Slide 111

Slide 111 text

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

Slide 112

Slide 112 text

custom features

Slide 113

Slide 113 text

extend state

Slide 114

Slide 114 text

No content

Slide 115

Slide 115 text

No content

Slide 116

Slide 116 text

demo

Slide 117

Slide 117 text

No content

Slide 118

Slide 118 text

https://github.com/ngrx/platform/issues/4580

Slide 119

Slide 119 text

No content

Slide 120

Slide 120 text

actions

Slide 121

Slide 121 text

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 }>(), }, });

Slide 122

Slide 122 text

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 }>(), }, });

Slide 123

Slide 123 text

reducer

Slide 124

Slide 124 text

export const productsReducer = createReducer( initialProductsState, on(ProductsActions.loadProductsSuccess, (state, { products }) => ({ ...state, products, })), );

Slide 125

Slide 125 text

import { when, withReducer } from '@ngrx/signals/events'; export const UsersStore = signalStore( { providedIn: 'root' }, withEntities(), withRequestStatus(), withReducer( when(usersPageEvents.opened, usersPageEvents.refreshed, setPending), when(usersApiEvents.usersLoadedSuccess, ({ users }) => [ setAllEntities(users), setFulfilled(), ]), when(usersApiEvents.usersLoadedError, ({ error }) => setError(error)), ), );

Slide 126

Slide 126 text

effects

Slide 127

Slide 127 text

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 }, );

Slide 128

Slide 128 text

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))), }), ), );

Slide 129

Slide 129 text

reading

Slide 130

Slide 130 text

@Component({ template: `
@for (productCategory of productsByCategories(); track productCategory.category ) { ... }
` }) export class ProductsComponent implements OnInit { private readonly store = inject(Store); readonly productsByCategories = this.store.selectSignal( selectProductsByCategories, ); }

Slide 131

Slide 131 text

@Component({ selector: 'app-users', template: `

Users

@if (usersStore.isPending()) {

Loading...

}
    @for (user of usersStore.entities(); track user.id) {
  • {{ user.name }}
  • }
`, }) export class UsersComponent { readonly usersStore = inject(UsersStore); }

Slide 132

Slide 132 text

dispatching events

Slide 133

Slide 133 text

@Component({ template: `...` }) export class ProductsComponent implements OnInit { private readonly store = inject(Store); ngOnInit(): void { this.store.dispatch(ProductsActions.loadProducts()); } }

Slide 134

Slide 134 text

@Component({ template: `

Users

Refresh `, }) 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()); } }

Slide 135

Slide 135 text

https://github.com/ngrx/platform/issues/4580

Slide 136

Slide 136 text

classic store

Slide 137

Slide 137 text

signal store classic store

Slide 138

Slide 138 text

signal store signal store events plugin classic store

Slide 139

Slide 139 text

signal store signal store events plugin classic store

Slide 140

Slide 140 text

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

Slide 141

Slide 141 text

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

Slide 142

Slide 142 text

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

Slide 143

Slide 143 text

you have to deal with state

Slide 144

Slide 144 text

https://github.com/FabianGosebrink/ngrx-signal-store-todo https://github.com/FabianGosebrink/angular-todo-signals

Slide 145

Slide 145 text

No content