Skip to content

Commit 3af1288

Browse files
fix: build error in signal forms + API error handling
Signal forms fix: - submit() action receives FieldTree not plain model; read values via field.title().value() and return Promise.resolve() Error handling: - Add mutationError: string | null to TodoState - Mutation tapResponse.error sets a user-friendly message in state - Mutation tapResponse.next clears mutationError - Add clearMutationError() store method - TodoPage shows dismissible error banner for mutation errors - TodoPage shows fetch error alert (cloud_off + retry button) when store.todos.error() is set, replacing the list Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
1 parent 932d651 commit 3af1288

3 files changed

Lines changed: 108 additions & 18 deletions

File tree

libs/todo/src/lib/components/todo-form/todo-form.ts

Lines changed: 4 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -93,13 +93,14 @@ export class TodoForm {
9393

9494
handleSubmit(event: SubmitEvent): void {
9595
event.preventDefault();
96-
submit(this.todoForm, (value) => {
96+
submit(this.todoForm, (field) => {
9797
this.create.emit({
98-
title: value.title.trim(),
99-
description: value.description.trim(),
98+
title: field.title().value().trim(),
99+
description: field.description().value().trim(),
100100
completed: false,
101101
});
102102
this.model.set({ title: '', description: '' });
103+
return Promise.resolve();
103104
});
104105
}
105106
}

libs/todo/src/lib/components/todo-page/todo-page.ts

Lines changed: 66 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -1,22 +1,81 @@
11
import { ChangeDetectionStrategy, Component, inject } from '@angular/core';
2+
import { MatIconButton } from '@angular/material/button';
3+
import { MatIcon } from '@angular/material/icon';
24
import { LayoutStore, PageContainer, PageToolbar } from '@myorg/shared';
35
import { TodoStore } from '../../state/todo.store';
46
import { TodoForm } from '../todo-form/todo-form';
57
import { TodoList } from '../todo-list/todo-list';
68

79
@Component({
810
selector: 'lib-todo-page',
9-
imports: [PageToolbar, PageContainer, TodoForm, TodoList],
11+
imports: [
12+
PageToolbar,
13+
PageContainer,
14+
TodoForm,
15+
TodoList,
16+
MatIcon,
17+
MatIconButton,
18+
],
1019
template: `
1120
<lib-page-toolbar [title]="layoutStore.title()" />
1221
<lib-page-container>
1322
<lib-todo-form class="mb-6" (create)="store.create($event)" />
14-
<lib-todo-list
15-
[todos]="store.todos.value() ?? []"
16-
[loading]="store.todos.isLoading()"
17-
(toggled)="store.toggle($event)"
18-
(removed)="store.remove($event)"
19-
/>
23+
24+
@if (store.mutationError()) {
25+
<div
26+
class="mb-4 flex items-start gap-3 rounded-xl border border-error bg-error-container p-4"
27+
role="alert"
28+
>
29+
<mat-icon class="mt-0.5 shrink-0 text-on-error-container"
30+
>error</mat-icon
31+
>
32+
<p class="flex-1 text-sm text-on-error-container">
33+
{{ store.mutationError() }}
34+
</p>
35+
<button
36+
mat-icon-button
37+
class="!-m-2 shrink-0 !text-on-error-container"
38+
aria-label="Dismiss error"
39+
(click)="store.clearMutationError()"
40+
>
41+
<mat-icon>close</mat-icon>
42+
</button>
43+
</div>
44+
}
45+
46+
@if (store.todos.error()) {
47+
<div
48+
class="mx-auto flex w-full max-w-md items-start gap-4 rounded-xl border border-error bg-error-container p-4"
49+
role="alert"
50+
>
51+
<mat-icon class="mt-0.5 shrink-0 text-on-error-container"
52+
>cloud_off</mat-icon
53+
>
54+
<div class="flex flex-1 flex-col gap-1">
55+
<p class="text-sm font-medium leading-none text-on-error-container">
56+
Could not load todos
57+
</p>
58+
<p class="text-sm text-on-error-container/80">
59+
The API could not be reached. Check your connection and try again.
60+
</p>
61+
</div>
62+
<button
63+
mat-icon-button
64+
class="!-m-2 shrink-0 !text-on-error-container"
65+
aria-label="Retry"
66+
(click)="store.reload()"
67+
>
68+
<mat-icon>refresh</mat-icon>
69+
</button>
70+
</div>
71+
} @else {
72+
<lib-todo-list
73+
[todos]="store.todos.value() ?? []"
74+
[loading]="store.todos.isLoading()"
75+
(toggled)="store.toggle($event)"
76+
(removed)="store.remove($event)"
77+
/>
78+
}
2079
</lib-page-container>
2180
`,
2281
host: {

libs/todo/src/lib/state/todo.store.ts

Lines changed: 38 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -19,10 +19,12 @@ import { TodoService } from '../services/todo.service';
1919

2020
export type TodoState = {
2121
syncEnabled: boolean;
22+
mutationError: string | null;
2223
};
2324

2425
export const todoInitialState: TodoState = {
2526
syncEnabled: false,
27+
mutationError: null,
2628
};
2729

2830
export function withTodoFeature() {
@@ -54,13 +56,23 @@ export function withTodoFeature() {
5456
todos.reload();
5557
},
5658

59+
clearMutationError() {
60+
patchState(store, { mutationError: null });
61+
},
62+
5763
create: rxMethod<CreateTodoRequest>(
5864
pipe(
5965
switchMap((todo) =>
6066
todoService.create(todo).pipe(
6167
tapResponse({
62-
next: () => todos.reload(),
63-
error: (error) => console.error('Failed to create todo', error),
68+
next: () => {
69+
patchState(store, { mutationError: null });
70+
todos.reload();
71+
},
72+
error: () =>
73+
patchState(store, {
74+
mutationError: 'Failed to create todo. Please try again.',
75+
}),
6476
}),
6577
),
6678
),
@@ -72,8 +84,14 @@ export function withTodoFeature() {
7284
switchMap(({ id, changes }) =>
7385
todoService.update(id, changes).pipe(
7486
tapResponse({
75-
next: () => todos.reload(),
76-
error: (error) => console.error('Failed to update todo', error),
87+
next: () => {
88+
patchState(store, { mutationError: null });
89+
todos.reload();
90+
},
91+
error: () =>
92+
patchState(store, {
93+
mutationError: 'Failed to update todo. Please try again.',
94+
}),
7795
}),
7896
),
7997
),
@@ -85,8 +103,14 @@ export function withTodoFeature() {
85103
switchMap((id) =>
86104
todoService.remove(id).pipe(
87105
tapResponse({
88-
next: () => todos.reload(),
89-
error: (error) => console.error('Failed to remove todo', error),
106+
next: () => {
107+
patchState(store, { mutationError: null });
108+
todos.reload();
109+
},
110+
error: () =>
111+
patchState(store, {
112+
mutationError: 'Failed to delete todo. Please try again.',
113+
}),
90114
}),
91115
),
92116
),
@@ -98,8 +122,14 @@ export function withTodoFeature() {
98122
switchMap((todo) =>
99123
todoService.update(todo.id, { completed: !todo.completed }).pipe(
100124
tapResponse({
101-
next: () => todos.reload(),
102-
error: (error) => console.error('Failed to toggle todo', error),
125+
next: () => {
126+
patchState(store, { mutationError: null });
127+
todos.reload();
128+
},
129+
error: () =>
130+
patchState(store, {
131+
mutationError: 'Failed to update todo. Please try again.',
132+
}),
103133
}),
104134
),
105135
),

0 commit comments

Comments
 (0)