@@ -4,21 +4,18 @@ import {
44 effect ,
55 inject ,
66 input ,
7+ signal ,
78} from '@angular/core' ;
89import { rxResource } from '@angular/core/rxjs-interop' ;
9- import {
10- FormControl ,
11- FormGroup ,
12- ReactiveFormsModule ,
13- Validators ,
14- } from '@angular/forms' ;
10+ import { form , FormField , min , required , submit } from '@angular/forms/signals' ;
1511import { Router } from '@angular/router' ;
16- import { of } from 'rxjs' ;
12+ import { lastValueFrom , of } from 'rxjs' ;
1713import { FakeBackendService } from './fake-backend.service' ;
14+ import { User } from './user.model' ;
1815
1916@Component ( {
2017 selector : 'app-user-form' ,
21- imports : [ ReactiveFormsModule ] ,
18+ imports : [ FormField ] ,
2219 template : `
2320 <div class="rounded-lg border border-gray-200 bg-white p-6 shadow-sm">
2421 <h2 class="mb-4 text-xl font-semibold text-gray-800">
@@ -30,7 +27,7 @@ import { FakeBackendService } from './fake-backend.service';
3027 class="h-8 w-8 animate-spin rounded-full border-4 border-indigo-500 border-t-transparent"></div>
3128 </div>
3229 } @else {
33- <form [formGroup]="userForm" (ngSubmit )="onSubmit()" class="space-y-4">
30+ <form (submit )="onSubmit($event )" class="space-y-4">
3431 <div>
3532 <label
3633 for="firstname"
@@ -40,15 +37,18 @@ import { FakeBackendService } from './fake-backend.service';
4037 <input
4138 id="firstname"
4239 type="text"
43- formControlName="firstname "
44- class="mt-1 block w-full rounded-md border border-gray-300 px-3 py-2 shadow-sm focus:border-indigo-500 focus:outline-none focus:ring-indigo-500 sm:text-sm " />
40+ class="mt-1 block w-full rounded-md border border-gray-300 px-3 py-2 shadow-sm focus:border-indigo-500 focus:ring-indigo-500 focus:outline-none sm:text-sm "
41+ [formField]="userSignalForm.firstname " />
4542 @if (
46- userForm.get(' firstname')? .invalid &&
47- userForm.get(' firstname')? .touched
43+ userSignalForm. firstname() .invalid() &&
44+ userSignalForm. firstname() .touched()
4845 ) {
49- <p class="mt-1 text-xs text-red-500">Firstname is required</p>
46+ @for (error of userSignalForm.firstname().errors(); track error) {
47+ <p class="mt-1 text-xs text-red-500">{{ error.message }}</p>
48+ }
5049 }
5150 </div>
51+
5252 <div>
5353 <label
5454 for="lastname"
@@ -58,49 +58,59 @@ import { FakeBackendService } from './fake-backend.service';
5858 <input
5959 id="lastname"
6060 type="text"
61- formControlName="lastname "
62- class="mt-1 block w-full rounded-md border border-gray-300 px-3 py-2 shadow-sm focus:border-indigo-500 focus:outline-none focus:ring-indigo-500 sm:text-sm " />
61+ class="mt-1 block w-full rounded-md border border-gray-300 px-3 py-2 shadow-sm focus:border-indigo-500 focus:ring-indigo-500 focus:outline-none sm:text-sm "
62+ [formField]="userSignalForm.lastname " />
6363 @if (
64- userForm.get(' lastname')? .invalid &&
65- userForm.get(' lastname')? .touched
64+ userSignalForm. lastname() .invalid() &&
65+ userSignalForm. lastname() .touched()
6666 ) {
67- <p class="mt-1 text-xs text-red-500">Lastname is required</p>
67+ @for (error of userSignalForm.lastname().errors(); track error) {
68+ <p class="mt-1 text-xs text-red-500">{{ error.message }}</p>
69+ }
6870 }
6971 </div>
72+
7073 <div>
7174 <label for="age" class="block text-sm font-medium text-gray-700">
7275 Age
7376 </label>
7477 <input
7578 id="age"
7679 type="number"
77- formControlName="age"
78- class="mt-1 block w-full rounded-md border border-gray-300 px-3 py-2 shadow-sm focus:border-indigo-500 focus:outline-none focus:ring-indigo-500 sm:text-sm" />
79- @if (userForm.get('age')?.invalid && userForm.get('age')?.touched) {
80- <p class="mt-1 text-xs text-red-500">Age must be positive</p>
80+ class="mt-1 block w-full rounded-md border border-gray-300 px-3 py-2 shadow-sm focus:border-indigo-500 focus:ring-indigo-500 focus:outline-none sm:text-sm"
81+ [formField]="userSignalForm.age" />
82+ @if (
83+ userSignalForm.age().invalid() && userSignalForm.age().touched()
84+ ) {
85+ @for (error of userSignalForm.age().errors(); track error) {
86+ <p class="mt-1 text-xs text-red-500">{{ error.message }}</p>
87+ }
8188 }
8289 </div>
90+
8391 <div>
8492 <label for="grade" class="block text-sm font-medium text-gray-700">
8593 Grade
8694 </label>
8795 <input
8896 id="grade"
8997 type="number"
90- formControlName="grade "
91- class="mt-1 block w-full rounded-md border border-gray-300 px-3 py-2 shadow-sm focus:border-indigo-500 focus:outline-none focus:ring-indigo-500 sm:text-sm " />
98+ class="mt-1 block w-full rounded-md border border-gray-300 px-3 py-2 shadow-sm focus:border-indigo-500 focus:ring-indigo-500 focus:outline-none sm:text-sm "
99+ [formField]="userSignalForm.grade " />
92100 </div>
101+
93102 <div class="flex justify-end space-x-3 pt-4">
94103 <button
95104 type="button"
96105 (click)="onCancel()"
97- class="rounded-md border border-gray-300 bg-white px-4 py-2 text-sm font-medium text-gray-700 hover:bg-gray-50 focus:outline-none focus: ring-2 focus:ring-indigo-500 focus:ring-offset-2">
106+ class="rounded-md border border-gray-300 bg-white px-4 py-2 text-sm font-medium text-gray-700 hover:bg-gray-50 focus:ring-2 focus:ring-indigo-500 focus:ring-offset-2 focus:outline-none ">
98107 Cancel
99108 </button>
109+
100110 <button
101111 type="submit"
102- [disabled]="userForm .invalid"
103- class="rounded-md border border-transparent bg-indigo-600 px-4 py-2 text-sm font-medium text-white shadow-sm hover:bg-indigo-700 focus:outline-none focus: ring-2 focus:ring-indigo-500 focus:ring-offset-2 disabled:opacity-50">
112+ [disabled]="userSignalForm() .invalid() "
113+ class="rounded-md border border-transparent bg-indigo-600 px-4 py-2 text-sm font-medium text-white shadow-sm hover:bg-indigo-700 focus:ring-2 focus:ring-indigo-500 focus:ring-offset-2 focus:outline-none disabled:opacity-50">
104114 {{ id() ? 'Update' : 'Add' }}
105115 </button>
106116 </div>
@@ -124,50 +134,51 @@ export class UserFormComponent {
124134 defaultValue : undefined ,
125135 } ) ;
126136
127- userForm = new FormGroup ( {
128- firstname : new FormControl ( '' , {
129- nonNullable : true ,
130- validators : [ Validators . required ] ,
131- } ) ,
132- lastname : new FormControl ( '' , {
133- nonNullable : true ,
134- validators : [ Validators . required ] ,
135- } ) ,
136- age : new FormControl ( 0 , {
137- nonNullable : true ,
138- validators : [ Validators . required , Validators . min ( 0 ) ] ,
139- } ) ,
140- grade : new FormControl ( 0 , {
141- nonNullable : true ,
142- validators : [ Validators . required ] ,
143- } ) ,
137+ userModel = signal < Omit < User , 'id' > > ( {
138+ firstname : '' ,
139+ lastname : '' ,
140+ age : 0 ,
141+ grade : 0 ,
142+ } ) ;
143+
144+ userSignalForm = form ( this . userModel , ( schemePath ) => {
145+ required ( schemePath . firstname , { message : 'Firstname is required' } ) ;
146+ required ( schemePath . lastname , { message : 'Lastname is required' } ) ;
147+ required ( schemePath . age ) ;
148+ min ( schemePath . age , 0 , { message : 'Age must be positive' } ) ;
149+ required ( schemePath . grade ) ;
144150 } ) ;
145151
146152 constructor ( ) {
147153 effect ( ( ) => {
148154 const userValue = this . userResource . value ( ) ;
149155 if ( userValue ) {
150- this . userForm . patchValue ( userValue ) ;
156+ this . userModel . set ( userValue ) ;
151157 } else {
152- this . userForm . reset ( { firstname : '' , lastname : '' , age : 0 , grade : 0 } ) ;
158+ this . userModel . set ( { firstname : '' , lastname : '' , age : 0 , grade : 0 } ) ;
153159 }
154160 } ) ;
155161 }
156162
157- onSubmit ( ) : void {
158- if ( this . userForm . valid ) {
163+ onSubmit ( event : Event ) : void {
164+ event . preventDefault ( ) ;
165+
166+ submit ( this . userSignalForm , async ( ) => {
159167 const userValue = this . userResource . value ( ) ;
160- const obs = userValue
161- ? this . backend . updateUser ( {
162- ...this . userForm . getRawValue ( ) ,
168+
169+ if ( userValue ) {
170+ await lastValueFrom (
171+ this . backend . updateUser ( {
172+ ...this . userModel ( ) ,
163173 id : userValue . id ,
164- } )
165- : this . backend . addUser ( this . userForm . getRawValue ( ) ) ;
174+ } ) ,
175+ ) ;
176+ } else {
177+ await lastValueFrom ( this . backend . addUser ( this . userModel ( ) ) ) ;
178+ }
166179
167- obs . subscribe ( ( ) => {
168- this . router . navigate ( [ '/' ] ) ;
169- } ) ;
170- }
180+ this . router . navigate ( [ '/' ] ) ;
181+ } ) ;
171182 }
172183
173184 onCancel ( ) : void {
0 commit comments