Skip to content

Commit 9a1791d

Browse files
committed
feat(answer61): simple form signal challenge
1 parent fbb5e82 commit 9a1791d

File tree

1 file changed

+50
-47
lines changed

1 file changed

+50
-47
lines changed
Lines changed: 50 additions & 47 deletions
Original file line numberDiff line numberDiff line change
@@ -1,21 +1,23 @@
11
import { JsonPipe } from '@angular/common';
2-
import { Component, signal, WritableSignal } from '@angular/core';
3-
import {
4-
FormControl,
5-
FormGroup,
6-
ReactiveFormsModule,
7-
Validators,
8-
} from '@angular/forms';
2+
import { Component, signal } from '@angular/core';
3+
import { Field, form, max, min, required } from '@angular/forms/signals';
4+
5+
interface FormData {
6+
name: string;
7+
lastname: string;
8+
age: number;
9+
note: string;
10+
}
911

1012
@Component({
1113
selector: 'app-root',
12-
imports: [ReactiveFormsModule, JsonPipe],
14+
imports: [JsonPipe, Field],
1315
template: `
1416
<div class="min-h-screen bg-gray-100 px-4 py-12 sm:px-6 lg:px-8">
1517
<div class="mx-auto max-w-md rounded-lg bg-white p-8 shadow-md">
1618
<h1 class="mb-6 text-3xl font-bold text-gray-900">Simple Form</h1>
1719
18-
<form [formGroup]="form" (ngSubmit)="onSubmit()" class="space-y-6">
20+
<form (submit)="onSubmit()" class="space-y-6">
1921
<div>
2022
<label
2123
for="name"
@@ -26,14 +28,18 @@ import {
2628
<input
2729
id="name"
2830
type="text"
29-
formControlName="name"
31+
[field]="form.name"
3032
placeholder="Enter your name"
3133
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"
3234
[class.border-red-500]="
33-
form.controls.name.invalid && !form.controls.name.untouched
35+
form.name().invalid() && form.name().touched()
3436
" />
35-
@if (form.controls.name.invalid && !form.controls.name.untouched) {
36-
<p class="mt-1 text-sm text-red-600">Name is required</p>
37+
@if (form.name().invalid() && form.name().touched()) {
38+
<p class="mt-1 text-sm text-red-600">
39+
@for (error of form.name().errors(); track error) {
40+
{{ error.message }}
41+
}
42+
</p>
3743
}
3844
</div>
3945
@@ -46,7 +52,7 @@ import {
4652
<input
4753
id="lastname"
4854
type="text"
49-
formControlName="lastname"
55+
[field]="form.lastname"
5056
placeholder="Enter your last name"
5157
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" />
5258
</div>
@@ -60,21 +66,16 @@ import {
6066
<input
6167
id="age"
6268
type="number"
63-
formControlName="age"
69+
[field]="form.age"
6470
placeholder="Enter your age (1-99)"
65-
min="1"
66-
max="99"
6771
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"
6872
[class.border-red-500]="
69-
form.controls.age.invalid && !form.controls.age.untouched
73+
form.age().invalid() && form.age().touched()
7074
" />
71-
@if (form.controls.age.invalid && !form.controls.age.untouched) {
75+
@if (form.age().invalid() && form.age().touched()) {
7276
<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
77+
@for (error of form.age().errors(); track error) {
78+
{{ error.message }}
7879
}
7980
</p>
8081
}
@@ -89,15 +90,15 @@ import {
8990
<input
9091
id="note"
9192
type="text"
92-
formControlName="note"
93+
[field]="form.note"
9394
placeholder="Enter a note"
9495
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" />
9596
</div>
9697
9798
<div class="flex gap-4">
9899
<button
99100
type="submit"
100-
[disabled]="form.invalid"
101+
[disabled]="form().invalid()"
101102
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">
102103
Submit
103104
</button>
@@ -126,35 +127,37 @@ import {
126127
`,
127128
})
128129
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 }),
130+
// we cannot use null for initial value of number field because null is not supported by native input[type="number"]
131+
// https://github.com/angular/angular/issues/65454
132+
formModel = signal<FormData>({
133+
name: '',
134+
lastname: '',
135+
age: NaN,
136+
note: '',
137+
});
138+
139+
form = form<FormData>(this.formModel, (path) => {
140+
required(path.name, { message: 'Name is required' });
141+
min(path.age, 1, { message: 'Age must be at least 1' });
142+
max(path.age, 99, { message: 'Age must be at most 99' });
140143
});
141144

142-
submittedData: WritableSignal<{
143-
name: string;
144-
lastname: string;
145-
age: number | null;
146-
note: string;
147-
} | null> = signal(null);
145+
submittedData = signal<FormData | null>(null);
148146

149147
onSubmit(): void {
150-
if (this.form.valid) {
151-
this.submittedData.set(this.form.getRawValue());
152-
console.log('Form submitted:', this.submittedData);
148+
if (this.form().valid()) {
149+
this.submittedData.set(this.formModel());
150+
console.log('Form submitted:', this.submittedData());
153151
}
154152
}
155153

156154
onReset(): void {
157-
this.form.reset();
155+
this.formModel.set({
156+
name: '',
157+
lastname: '',
158+
age: NaN,
159+
note: '',
160+
});
158161
this.submittedData.set(null);
159162
}
160163
}

0 commit comments

Comments
 (0)