From 5113d8bc22d37047510c7d7262493fbca5cd4121 Mon Sep 17 00:00:00 2001 From: Hien Pham Date: Tue, 24 Mar 2026 20:18:30 +0200 Subject: [PATCH 1/2] feat(Answer:65): refactor to use signal form --- .../src/app/user-form.component.ts | 127 ++++++++++-------- 1 file changed, 69 insertions(+), 58 deletions(-) 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..97c7148d3 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 @@ -4,21 +4,18 @@ import { effect, inject, input, + signal, } 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 { lastValueFrom, of } from 'rxjs'; import { FakeBackendService } from './fake-backend.service'; +import { User } from './user.model'; @Component({ selector: 'app-user-form', - imports: [ReactiveFormsModule], + imports: [FormField], template: `

@@ -30,7 +27,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 { -
+
+
+
+
+
+
@@ -124,50 +134,51 @@ 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 = signal>({ + firstname: '', + lastname: '', + age: 0, + grade: 0, + }); + + userSignalForm = form(this.userModel, (schemePath) => { + required(schemePath.firstname, { message: 'Firstname is required' }); + required(schemePath.lastname, { message: 'Lastname is required' }); + required(schemePath.age); + min(schemePath.age, 0, { message: 'Age must be positive' }); + required(schemePath.grade); }); constructor() { effect(() => { const userValue = this.userResource.value(); if (userValue) { - this.userForm.patchValue(userValue); + this.userModel.set(userValue); } else { - this.userForm.reset({ firstname: '', lastname: '', age: 0, grade: 0 }); + this.userModel.set({ firstname: '', lastname: '', age: 0, grade: 0 }); } }); } - onSubmit(): void { - if (this.userForm.valid) { + onSubmit(event: Event): void { + event.preventDefault(); + + submit(this.userSignalForm, async () => { const userValue = this.userResource.value(); - const obs = userValue - ? this.backend.updateUser({ - ...this.userForm.getRawValue(), + + if (userValue) { + await lastValueFrom( + this.backend.updateUser({ + ...this.userModel(), id: userValue.id, - }) - : this.backend.addUser(this.userForm.getRawValue()); + }), + ); + } else { + await lastValueFrom(this.backend.addUser(this.userModel())); + } - obs.subscribe(() => { - this.router.navigate(['/']); - }); - } + this.router.navigate(['/']); + }); } onCancel(): void { From 560ad5514be01b44652d01197670e613dfe9af08 Mon Sep 17 00:00:00 2001 From: Hien Pham Date: Wed, 25 Mar 2026 20:59:34 +0200 Subject: [PATCH 2/2] feat(Answer:65): use linkedSignal to set value of userModel --- .../src/app/user-form.component.ts | 35 +++++++++---------- 1 file changed, 17 insertions(+), 18 deletions(-) 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 97c7148d3..e1fd4f835 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,10 +1,9 @@ import { ChangeDetectionStrategy, Component, - effect, inject, input, - signal, + linkedSignal, } from '@angular/core'; import { rxResource } from '@angular/core/rxjs-interop'; import { form, FormField, min, required, submit } from '@angular/forms/signals'; @@ -134,11 +133,22 @@ export class UserFormComponent { defaultValue: undefined, }); - userModel = signal>({ - firstname: '', - lastname: '', - age: 0, - grade: 0, + userModel = linkedSignal>(() => { + const hasValue = this.userResource.hasValue(); + const user = this.userResource.value(); + + if (hasValue && user) { + // eslint-disable-next-line @typescript-eslint/no-unused-vars + const { id, ...rest } = user; + return rest; + } else { + return { + firstname: '', + lastname: '', + age: 0, + grade: 0, + }; + } }); userSignalForm = form(this.userModel, (schemePath) => { @@ -149,17 +159,6 @@ export class UserFormComponent { required(schemePath.grade); }); - constructor() { - effect(() => { - const userValue = this.userResource.value(); - if (userValue) { - this.userModel.set(userValue); - } else { - this.userModel.set({ firstname: '', lastname: '', age: 0, grade: 0 }); - } - }); - } - onSubmit(event: Event): void { event.preventDefault();