Skip to content

Commit ab3b9cb

Browse files
committed
feat(challenge 61): refactor simple form from reactive form to use signal forms
1 parent 543770b commit ab3b9cb

1 file changed

Lines changed: 61 additions & 56 deletions

File tree

Lines changed: 61 additions & 56 deletions
Original file line numberDiff line numberDiff line change
@@ -1,21 +1,38 @@
11
import { JsonPipe } from '@angular/common';
22
import { Component, signal, WritableSignal } from '@angular/core';
3+
34
import {
4-
FormControl,
5-
FormGroup,
6-
ReactiveFormsModule,
7-
Validators,
8-
} from '@angular/forms';
5+
form,
6+
FormField,
7+
FormRoot,
8+
max,
9+
min,
10+
required,
11+
} from '@angular/forms/signals';
12+
13+
type FormData = {
14+
name: string;
15+
lastname: string;
16+
age: number;
17+
note: string;
18+
};
19+
20+
const initialFormData: FormData = {
21+
name: '',
22+
lastname: '',
23+
age: NaN,
24+
note: '',
25+
};
926

1027
@Component({
1128
selector: 'app-root',
12-
imports: [ReactiveFormsModule, JsonPipe],
29+
imports: [FormField, FormRoot, JsonPipe],
1330
template: `
1431
<div class="min-h-screen bg-gray-100 px-4 py-12 sm:px-6 lg:px-8">
1532
<div class="mx-auto max-w-md rounded-lg bg-white p-8 shadow-md">
1633
<h1 class="mb-6 text-3xl font-bold text-gray-900">Simple Form</h1>
1734
18-
<form [formGroup]="form" (ngSubmit)="onSubmit()" class="space-y-6">
35+
<form [formRoot]="form" class="space-y-6">
1936
<div>
2037
<label
2138
for="name"
@@ -26,14 +43,16 @@ import {
2643
<input
2744
id="name"
2845
type="text"
29-
formControlName="name"
46+
[formField]="form.name"
3047
placeholder="Enter your name"
31-
class="w-full rounded-md border border-gray-300 px-4 py-2 outline-none transition focus:border-blue-500 focus:ring-2 focus:ring-blue-500"
48+
class="w-full rounded-md border border-gray-300 px-4 py-2 transition outline-none focus:border-blue-500 focus:ring-2 focus:ring-blue-500"
3249
[class.border-red-500]="
33-
form.controls.name.invalid && !form.controls.name.untouched
50+
form.name().invalid() && form.name().touched()
3451
" />
35-
@if (form.controls.name.invalid && !form.controls.name.untouched) {
36-
<p class="mt-1 text-sm text-red-600">Name is required</p>
52+
@if (form.name().invalid() && form.name().touched()) {
53+
@for (error of form.name().errors(); track error) {
54+
<p class="mt-1 text-sm text-red-600">{{ error.message }}</p>
55+
}
3756
}
3857
</div>
3958
@@ -46,9 +65,9 @@ import {
4665
<input
4766
id="lastname"
4867
type="text"
49-
formControlName="lastname"
68+
[formField]="form.lastname"
5069
placeholder="Enter your last name"
51-
class="w-full rounded-md border border-gray-300 px-4 py-2 outline-none transition focus:border-blue-500 focus:ring-2 focus:ring-blue-500" />
70+
class="w-full rounded-md border border-gray-300 px-4 py-2 transition outline-none focus:border-blue-500 focus:ring-2 focus:ring-blue-500" />
5271
</div>
5372
5473
<div>
@@ -60,23 +79,16 @@ import {
6079
<input
6180
id="age"
6281
type="number"
63-
formControlName="age"
82+
[formField]="form.age"
6483
placeholder="Enter your age (1-99)"
65-
min="1"
66-
max="99"
67-
class="w-full rounded-md border border-gray-300 px-4 py-2 outline-none transition focus:border-blue-500 focus:ring-2 focus:ring-blue-500"
84+
class="w-full rounded-md border border-gray-300 px-4 py-2 transition outline-none focus:border-blue-500 focus:ring-2 focus:ring-blue-500"
6885
[class.border-red-500]="
69-
form.controls.age.invalid && !form.controls.age.untouched
86+
form.age().invalid() && form.age().touched()
7087
" />
71-
@if (form.controls.age.invalid && !form.controls.age.untouched) {
72-
<p class="mt-1 text-sm text-red-600">
73-
@if (form.controls.age.hasError('min')) {
74-
Age must be at least 1
75-
}
76-
@if (form.controls.age.hasError('max')) {
77-
Age must be at most 99
78-
}
79-
</p>
88+
@if (form.age().invalid() && form.age().touched()) {
89+
@for (error of form.age().errors(); track error) {
90+
<p class="mt-1 text-sm text-red-600">{{ error.message }}</p>
91+
}
8092
}
8193
</div>
8294
@@ -89,15 +101,15 @@ import {
89101
<input
90102
id="note"
91103
type="text"
92-
formControlName="note"
104+
[formField]="form.note"
93105
placeholder="Enter a note"
94-
class="w-full rounded-md border border-gray-300 px-4 py-2 outline-none transition focus:border-blue-500 focus:ring-2 focus:ring-blue-500" />
106+
class="w-full rounded-md border border-gray-300 px-4 py-2 transition outline-none focus:border-blue-500 focus:ring-2 focus:ring-blue-500" />
95107
</div>
96108
97109
<div class="flex gap-4">
98110
<button
99111
type="submit"
100-
[disabled]="form.invalid"
112+
[disabled]="form().invalid()"
101113
class="flex-1 rounded-md bg-blue-600 px-4 py-2 font-medium text-white transition hover:bg-blue-700 disabled:cursor-not-allowed disabled:bg-gray-400">
102114
Submit
103115
</button>
@@ -126,35 +138,28 @@ import {
126138
`,
127139
})
128140
export class AppComponent {
129-
form = new FormGroup({
130-
name: new FormControl('', {
131-
validators: Validators.required,
132-
nonNullable: true,
133-
}),
134-
lastname: new FormControl('', { nonNullable: true }),
135-
age: new FormControl<number | null>(null, [
136-
Validators.min(1),
137-
Validators.max(99),
138-
]),
139-
note: new FormControl('', { nonNullable: true }),
140-
});
141+
model = signal<FormData>(initialFormData);
141142

142-
submittedData: WritableSignal<{
143-
name: string;
144-
lastname: string;
145-
age: number | null;
146-
note: string;
147-
} | null> = signal(null);
143+
form = form(
144+
this.model,
145+
(schemaPath) => {
146+
required(schemaPath.name, { message: 'Name is required' });
147+
min(schemaPath.age, 1, { message: 'Age must be at least 1' });
148+
max(schemaPath.age, 99, { message: 'Age must be at most 99' });
149+
},
150+
{
151+
submission: {
152+
action: async (f) => {
153+
this.submittedData.set(f().value());
154+
},
155+
},
156+
},
157+
);
148158

149-
onSubmit(): void {
150-
if (this.form.valid) {
151-
this.submittedData.set(this.form.getRawValue());
152-
console.log('Form submitted:', this.submittedData);
153-
}
154-
}
159+
submittedData: WritableSignal<FormData | null> = signal(null);
155160

156161
onReset(): void {
157-
this.form.reset();
162+
this.form().reset(initialFormData);
158163
this.submittedData.set(null);
159164
}
160165
}

0 commit comments

Comments
 (0)