11import { JsonPipe } from '@angular/common' ;
22import { Component , signal , WritableSignal } from '@angular/core' ;
3- import {
4- FormControl ,
5- FormGroup ,
6- ReactiveFormsModule ,
7- Validators ,
8- } from '@angular/forms' ;
3+ import { Field , form , max , min , required } from '@angular/forms/signals' ;
4+
5+ interface IFormData {
6+ name : string ;
7+ lastname : string ;
8+ age : number ;
9+ note : string ;
10+ }
11+
12+ const defaultValue = {
13+ name : '' ,
14+ lastname : '' ,
15+ age : null ! ,
16+ note : '' ,
17+ } ;
918
1019@Component ( {
1120 selector : 'app-root' ,
12- imports : [ ReactiveFormsModule , JsonPipe ] ,
21+ imports : [ JsonPipe , Field ] ,
1322 template : `
1423 <div class="min-h-screen bg-gray-100 px-4 py-12 sm:px-6 lg:px-8">
1524 <div class="mx-auto max-w-md rounded-lg bg-white p-8 shadow-md">
1625 <h1 class="mb-6 text-3xl font-bold text-gray-900">Simple Form</h1>
1726
18- <form [formGroup]="form" (ngSubmit )="onSubmit()" class="space-y-6">
27+ <form (submit )="onSubmit($event )" class="space-y-6">
1928 <div>
2029 <label
2130 for="name"
@@ -26,14 +35,16 @@ import {
2635 <input
2736 id="name"
2837 type="text"
29- formControlName=" name"
38+ [field]="form. name"
3039 placeholder="Enter your name"
3140 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"
3241 [class.border-red-500]="
33- form.controls. name.invalid && ! form.controls. name.untouched
42+ form.name() .invalid() && form.name().touched()
3443 " />
35- @if (form.controls.name.invalid && !form.controls.name.untouched) {
36- <p class="mt-1 text-sm text-red-600">Name is required</p>
44+ @if (form.name().invalid() && form.name().touched()) {
45+ @for (error of form.name().errors(); track $index) {
46+ <p class="mt-1 text-sm text-red-600">{{ error.message }}</p>
47+ }
3748 }
3849 </div>
3950
@@ -46,7 +57,7 @@ import {
4657 <input
4758 id="lastname"
4859 type="text"
49- formControlName=" lastname"
60+ [field]="form. lastname"
5061 placeholder="Enter your last name"
5162 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" />
5263 </div>
@@ -60,21 +71,16 @@ import {
6071 <input
6172 id="age"
6273 type="number"
63- formControlName=" age"
74+ [field]="form. age"
6475 placeholder="Enter your age (1-99)"
65- min="1"
66- max="99"
6776 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"
6877 [class.border-red-500]="
69- form.controls. age.invalid && ! form.controls. age.untouched
78+ form.age() .invalid() && form.age().touched()
7079 " />
71- @if (form.controls. age.invalid && ! form.controls. age.untouched ) {
80+ @if (form.age() .invalid() && form.age().touched() ) {
7281 <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
82+ @for (error of form.age().errors(); track $index) {
83+ <p class="mt-1 text-sm text-red-600">{{ error.message }}</p>
7884 }
7985 </p>
8086 }
@@ -89,15 +95,15 @@ import {
8995 <input
9096 id="note"
9197 type="text"
92- formControlName=" note"
98+ [field]="form. note"
9399 placeholder="Enter a note"
94100 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" />
95101 </div>
96102
97103 <div class="flex gap-4">
98104 <button
99105 type="submit"
100- [disabled]="form.invalid"
106+ [disabled]="form() .invalid() "
101107 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">
102108 Submit
103109 </button>
@@ -126,35 +132,29 @@ import {
126132 ` ,
127133} )
128134export 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 } ) ,
135+ model = signal < IFormData > ( defaultValue ) ;
136+
137+ form = form ( this . model , ( schemaPath ) => {
138+ ( required ( schemaPath . name , { message : 'Name is required' } ) ,
139+ min ( schemaPath . age , 1 , { message : 'Age must be at least 1' } ) ,
140+ max ( schemaPath . age , 99 , { message : 'Age must be at most 99' } ) ) ;
140141 } ) ;
141142
142- submittedData : WritableSignal < {
143- name : string ;
144- lastname : string ;
145- age : number | null ;
146- note : string ;
147- } | null > = signal ( null ) ;
143+ submittedData : WritableSignal < IFormData | null > = signal ( null ) ;
148144
149- onSubmit ( ) : void {
150- if ( this . form . valid ) {
151- this . submittedData . set ( this . form . getRawValue ( ) ) ;
152- console . log ( 'Form submitted:' , this . submittedData ) ;
145+ onSubmit ( e : Event ) : void {
146+ e . preventDefault ( ) ;
147+ if ( this . form ( ) . valid ( ) ) {
148+ this . submittedData . set ( {
149+ ...this . form ( ) . value ( ) ,
150+ age : + this . form ( ) . value ( ) . age ,
151+ } ) ;
152+ console . log ( 'Form submitted:' , this . submittedData ( ) ) ;
153153 }
154154 }
155155
156156 onReset ( ) : void {
157- this . form . reset ( ) ;
157+ this . form ( ) . reset ( defaultValue ) ;
158158 this . submittedData . set ( null ) ;
159159 }
160160}
0 commit comments