11import {
22 ChangeDetectionStrategy ,
33 Component ,
4- effect ,
54 inject ,
65 input ,
6+ linkedSignal ,
77} from '@angular/core' ;
88import { rxResource } from '@angular/core/rxjs-interop' ;
9- import {
10- FormControl ,
11- FormGroup ,
12- ReactiveFormsModule ,
13- Validators ,
14- } from '@angular/forms' ;
9+ import { form , FormField , min , required , submit } from '@angular/forms/signals' ;
1510import { Router } from '@angular/router' ;
1611import { of } from 'rxjs' ;
1712import { FakeBackendService } from './fake-backend.service' ;
13+ import { EditingUser } from './user.model' ;
1814
1915@Component ( {
2016 selector : 'app-user-form' ,
21- imports : [ ReactiveFormsModule ] ,
17+ imports : [ FormField ] ,
2218 template : `
2319 <div class="rounded-lg border border-gray-200 bg-white p-6 shadow-sm">
2420 <h2 class="mb-4 text-xl font-semibold text-gray-800">
@@ -30,7 +26,7 @@ import { FakeBackendService } from './fake-backend.service';
3026 class="h-8 w-8 animate-spin rounded-full border-4 border-indigo-500 border-t-transparent"></div>
3127 </div>
3228 } @else {
33- <form [formGroup]="userForm" (ngSubmit )="onSubmit()" class="space-y-4">
29+ <form (submit )="onSubmit($event )" class="space-y-4">
3430 <div>
3531 <label
3632 for="firstname"
@@ -40,13 +36,14 @@ import { FakeBackendService } from './fake-backend.service';
4036 <input
4137 id="firstname"
4238 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" />
39+ [formField]="userForm. firstname"
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" />
4541 @if (
46- userForm.get('firstname')?.invalid &&
47- userForm.get('firstname')?.touched
42+ userForm.firstname().invalid() && userForm.firstname().touched()
4843 ) {
49- <p class="mt-1 text-xs text-red-500">Firstname is required</p>
44+ @for (error of userForm.firstname().errors(); track error) {
45+ <p class="mt-1 text-xs text-red-500">{{ error.message }}</p>
46+ }
5047 }
5148 </div>
5249 <div>
@@ -58,13 +55,14 @@ import { FakeBackendService } from './fake-backend.service';
5855 <input
5956 id="lastname"
6057 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" />
58+ [formField]="userForm. lastname"
59+ 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" />
6360 @if (
64- userForm.get('lastname')?.invalid &&
65- userForm.get('lastname')?.touched
61+ userForm.lastname().invalid() && userForm.lastname().touched()
6662 ) {
67- <p class="mt-1 text-xs text-red-500">Lastname is required</p>
63+ @for (error of userForm.lastname().errors(); track error) {
64+ <p class="mt-1 text-xs text-red-500">{{ error.message }}</p>
65+ }
6866 }
6967 </div>
7068 <div>
@@ -74,10 +72,12 @@ import { FakeBackendService } from './fake-backend.service';
7472 <input
7573 id="age"
7674 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>
75+ [formField]="userForm.age"
76+ 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" />
77+ @if (userForm.age().invalid() && userForm.age().touched()) {
78+ @for (error of userForm.age().errors(); track error) {
79+ <p class="mt-1 text-xs text-red-500">{{ error.message }}</p>
80+ }
8181 }
8282 </div>
8383 <div>
@@ -87,20 +87,25 @@ import { FakeBackendService } from './fake-backend.service';
8787 <input
8888 id="grade"
8989 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" />
90+ [formField]="userForm.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:ring-indigo-500 focus:outline-none sm:text-sm" />
92+ @if (userForm.grade().invalid() && userForm.grade().touched()) {
93+ @for (error of userForm.grade().errors(); track error) {
94+ <p class="mt-1 text-xs text-red-500">{{ error.message }}</p>
95+ }
96+ }
9297 </div>
9398 <div class="flex justify-end space-x-3 pt-4">
9499 <button
95100 type="button"
96101 (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">
102+ 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 ">
98103 Cancel
99104 </button>
100105 <button
101106 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">
107+ [disabled]="userForm() .invalid() "
108+ 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">
104109 {{ id() ? 'Update' : 'Add' }}
105110 </button>
106111 </div>
@@ -124,50 +129,56 @@ export class UserFormComponent {
124129 defaultValue : undefined ,
125130 } ) ;
126131
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- } ) ,
132+ userModel = linkedSignal < EditingUser > ( ( ) => {
133+ const user = this . userResource . value ( ) ;
134+ const defaultUser : EditingUser = {
135+ firstname : '' ,
136+ lastname : '' ,
137+ age : 0 ,
138+ grade : 0 ,
139+ } ;
140+
141+ return user
142+ ? {
143+ firstname : user . firstname ,
144+ lastname : user . lastname ,
145+ age : user . age ,
146+ grade : user . grade ,
147+ }
148+ : defaultUser ;
144149 } ) ;
145150
146- constructor ( ) {
147- effect ( ( ) => {
148- const userValue = this . userResource . value ( ) ;
149- if ( userValue ) {
150- this . userForm . patchValue ( userValue ) ;
151- } else {
152- this . userForm . reset ( { firstname : '' , lastname : '' , age : 0 , grade : 0 } ) ;
153- }
154- } ) ;
155- }
151+ userForm = form ( this . userModel , ( schemaPath ) => {
152+ required ( schemaPath . firstname , { message : 'First name is required' } ) ;
153+ required ( schemaPath . lastname , { message : 'Last name is required' } ) ;
154+ required ( schemaPath . age , { message : 'Age is required' } ) ;
155+ min ( schemaPath . age , 0 , { message : 'Age must be positive' } ) ;
156+ required ( schemaPath . grade , { message : 'Grade is required' } ) ;
157+ } ) ;
156158
157- onSubmit ( ) : void {
158- if ( this . userForm . valid ) {
159+ onSubmit ( event : Event ) : void {
160+ event . preventDefault ( ) ;
161+
162+ submit ( this . userForm , async ( ) => {
159163 const userValue = this . userResource . value ( ) ;
160- const obs = userValue
164+ const formValue = this . userModel ( ) ;
165+ const request = userValue
161166 ? this . backend . updateUser ( {
162- ...this . userForm . getRawValue ( ) ,
167+ ...formValue ,
163168 id : userValue . id ,
164169 } )
165- : this . backend . addUser ( this . userForm . getRawValue ( ) ) ;
170+ : this . backend . addUser ( formValue ) ;
166171
167- obs . subscribe ( ( ) => {
168- this . router . navigate ( [ '/' ] ) ;
172+ request . subscribe ( {
173+ next : ( ) => {
174+ this . router . navigate ( [ '/' ] ) ;
175+ } ,
176+ error : ( error ) => {
177+ console . error ( 'Error saving user:' , error ) ;
178+ // Here you could set an error state to display an error message in the UI
179+ } ,
169180 } ) ;
170- }
181+ } ) ;
171182 }
172183
173184 onCancel ( ) : void {
0 commit comments