diff --git a/apps/forms/65-signal-form-edition/src/app/user-form.component.ts b/apps/forms/65-signal-form-edition/src/app/user-form.component.ts index e9a6f91cb..aeb933550 100644 --- a/apps/forms/65-signal-form-edition/src/app/user-form.component.ts +++ b/apps/forms/65-signal-form-edition/src/app/user-form.component.ts @@ -1,24 +1,20 @@ import { ChangeDetectionStrategy, Component, - effect, inject, input, + linkedSignal, } from '@angular/core'; import { rxResource } from '@angular/core/rxjs-interop'; -import { - FormControl, - FormGroup, - ReactiveFormsModule, - Validators, -} from '@angular/forms'; +import { form, FormField, min, required, submit } from '@angular/forms/signals'; import { Router } from '@angular/router'; import { of } from 'rxjs'; import { FakeBackendService } from './fake-backend.service'; +import { EditingUser } from './user.model'; @Component({ selector: 'app-user-form', - imports: [ReactiveFormsModule], + imports: [FormField], template: `

@@ -30,7 +26,7 @@ import { FakeBackendService } from './fake-backend.service'; class="h-8 w-8 animate-spin rounded-full border-4 border-indigo-500 border-t-transparent">

} @else { -
+
@@ -58,13 +55,14 @@ import { FakeBackendService } from './fake-backend.service'; + [formField]="userForm.lastname" + 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" /> @if ( - userForm.get('lastname')?.invalid && - userForm.get('lastname')?.touched + userForm.lastname().invalid() && userForm.lastname().touched() ) { -

Lastname is required

+ @for (error of userForm.lastname().errors(); track error) { +

{{ error.message }}

+ } }
@@ -74,10 +72,12 @@ import { FakeBackendService } from './fake-backend.service'; - @if (userForm.get('age')?.invalid && userForm.get('age')?.touched) { -

Age must be positive

+ [formField]="userForm.age" + 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" /> + @if (userForm.age().invalid() && userForm.age().touched()) { + @for (error of userForm.age().errors(); track error) { +

{{ error.message }}

+ } }
@@ -87,20 +87,25 @@ import { FakeBackendService } from './fake-backend.service'; + [formField]="userForm.grade" + 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" /> + @if (userForm.grade().invalid() && userForm.grade().touched()) { + @for (error of userForm.grade().errors(); track error) { +

{{ error.message }}

+ } + }
@@ -124,50 +129,56 @@ export class UserFormComponent { defaultValue: undefined, }); - userForm = new FormGroup({ - firstname: new FormControl('', { - nonNullable: true, - validators: [Validators.required], - }), - lastname: new FormControl('', { - nonNullable: true, - validators: [Validators.required], - }), - age: new FormControl(0, { - nonNullable: true, - validators: [Validators.required, Validators.min(0)], - }), - grade: new FormControl(0, { - nonNullable: true, - validators: [Validators.required], - }), + userModel = linkedSignal(() => { + const user = this.userResource.value(); + const defaultUser: EditingUser = { + firstname: '', + lastname: '', + age: 0, + grade: 0, + }; + + return user + ? { + firstname: user.firstname, + lastname: user.lastname, + age: user.age, + grade: user.grade, + } + : defaultUser; }); - constructor() { - effect(() => { - const userValue = this.userResource.value(); - if (userValue) { - this.userForm.patchValue(userValue); - } else { - this.userForm.reset({ firstname: '', lastname: '', age: 0, grade: 0 }); - } - }); - } + userForm = form(this.userModel, (schemaPath) => { + required(schemaPath.firstname, { message: 'First name is required' }); + required(schemaPath.lastname, { message: 'Last name is required' }); + required(schemaPath.age, { message: 'Age is required' }); + min(schemaPath.age, 0, { message: 'Age must be positive' }); + required(schemaPath.grade, { message: 'Grade is required' }); + }); - onSubmit(): void { - if (this.userForm.valid) { + onSubmit(event: Event): void { + event.preventDefault(); + + submit(this.userForm, async () => { const userValue = this.userResource.value(); - const obs = userValue + const formValue = this.userModel(); + const request = userValue ? this.backend.updateUser({ - ...this.userForm.getRawValue(), + ...formValue, id: userValue.id, }) - : this.backend.addUser(this.userForm.getRawValue()); + : this.backend.addUser(formValue); - obs.subscribe(() => { - this.router.navigate(['/']); + request.subscribe({ + next: () => { + this.router.navigate(['/']); + }, + error: (error) => { + console.error('Error saving user:', error); + // Here you could set an error state to display an error message in the UI + }, }); - } + }); } onCancel(): void { diff --git a/apps/forms/65-signal-form-edition/src/app/user.model.ts b/apps/forms/65-signal-form-edition/src/app/user.model.ts index a4e26720a..439c06383 100644 --- a/apps/forms/65-signal-form-edition/src/app/user.model.ts +++ b/apps/forms/65-signal-form-edition/src/app/user.model.ts @@ -5,3 +5,5 @@ export interface User { age: number; grade: number; } + +export type EditingUser = Omit;