diff --git a/apps/forms/62-crossfield-validation-signal-form/src/app/app.component.html b/apps/forms/62-crossfield-validation-signal-form/src/app/app.component.html new file mode 100644 index 000000000..b23fa1bcb --- /dev/null +++ b/apps/forms/62-crossfield-validation-signal-form/src/app/app.component.html @@ -0,0 +1,175 @@ +
+
+

Registration Form

+

+ This form demonstrates cross field validation with reactive forms +

+ +
+
+ @let email = registrationForm.email(); + + + @if (email.invalid() && email.touched()) { + @for (error of email.errors(); track error) { +

{{ error.message }}

+ } + } +
+ +
+ @let password = registrationForm.password(); + + + @if (password.invalid() && password.touched()) { + @for (error of password.errors(); track error) { +

{{ error.message }}

+ } + } +
+ +
+ @let confirmPassword = registrationForm.confirmPassword(); + + + @if (confirmPassword.invalid() && confirmPassword.touched()) { + @for (error of confirmPassword.errors(); track error) { +

{{ error.message }}

+ } + } +
+ +
+ @let startDate = registrationForm.startDate(); + + + @if (startDate.invalid() && startDate.touched()) { + @for (error of startDate.errors(); track error) { +

{{ error.message }}

+ } + } +
+ +
+ @let endDate = registrationForm.endDate(); + + + @if (endDate.invalid() && endDate.touched()) { + @for (error of endDate.errors(); track error) { +

{{ error.message }}

+ } + } +
+ +
+ + +
+
+ +
+ @let form = registrationForm(); +

Form Status

+
+
+ Valid: + + {{ form.valid() ? 'Yes' : 'No' }} + +
+
+ Touched: + {{ form.touched() ? 'Yes' : 'No' }} +
+
+ Dirty: + {{ form.dirty() ? 'Yes' : 'No' }} +
+
+
+

Form Value:

+
{{ form.value() | json }}
+
+
+ + @if (isSubmitted()) { +
+

+ Form Submitted Successfully! +

+
{{ registrationForm().value() | json }}
+
+ } +
+
diff --git a/apps/forms/62-crossfield-validation-signal-form/src/app/app.component.ts b/apps/forms/62-crossfield-validation-signal-form/src/app/app.component.ts index 7d2005867..1b1c2a616 100644 --- a/apps/forms/62-crossfield-validation-signal-form/src/app/app.component.ts +++ b/apps/forms/62-crossfield-validation-signal-form/src/app/app.component.ts @@ -1,328 +1,104 @@ import { JsonPipe } from '@angular/common'; import { ChangeDetectionStrategy, Component, signal } from '@angular/core'; import { - AbstractControl, - FormControl, - FormGroup, - ReactiveFormsModule, - ValidationErrors, - ValidatorFn, - Validators, -} from '@angular/forms'; - -function passwordMatchValidator(): ValidatorFn { - return (control: AbstractControl): ValidationErrors | null => { - const form = control as FormGroup; - if (!form) { - return null; - } - - const password = form.value.password; - const confirmPassword = form.value.confirmPassword; - - if (!confirmPassword) { - return null; - } - - if (password !== confirmPassword) { - form.controls['confirmPassword'].setErrors({ passwordMismatch: true }); - } - - return null; - }; -} - -function endDateAfterStartDateValidator(): ValidatorFn { - return (control: AbstractControl): ValidationErrors | null => { - const form = control as FormGroup; - if (!form) { - return null; - } - - const startDate = form.value.startDate; - const endDate = form.value.endDate; - - if (!startDate || !endDate) { - return null; - } - - const start = new Date(startDate).getTime(); - const end = new Date(endDate).getTime(); - - if (end > start) { - form.controls['endDate'].setErrors(null); - } else { - form.controls['endDate'].setErrors({ endDateBeforeStart: true }); - } - - return null; - }; -} + FormField, + FormRoot, + email, + form, + minLength, + required, + validate, +} from '@angular/forms/signals'; + +type RegistrationData = { + email: string; + password: string; + confirmPassword: string; + startDate: string; + endDate: string; +}; @Component({ selector: 'app-root', - imports: [ReactiveFormsModule, JsonPipe], - template: ` -
-
-

Registration Form

-

- This form demonstrates cross field validation with reactive forms -

- -
-
- - - @if ( - form.controls.email.invalid && !form.controls.email.untouched - ) { -

- @if (form.controls.email.hasError('required')) { - Email is required - } @else if (form.controls.email.hasError('email')) { - Please enter a valid email address - } -

- } -
- -
- - - @if ( - form.controls.password.invalid && - !form.controls.password.untouched - ) { -

- @if (form.controls.password.hasError('required')) { - Password is required - } @else if (form.controls.password.hasError('minlength')) { - Password must be at least 6 characters - } -

- } -
- -
- - - @if ( - form.controls.confirmPassword.invalid && - !form.controls.confirmPassword.untouched - ) { -

- @if (form.controls.confirmPassword.hasError('required')) { - Please confirm your password - } @else if ( - form.controls.confirmPassword.hasError('passwordMismatch') - ) { - Passwords do not match - } -

- } -
- -
- - - @if ( - form.controls.startDate.invalid && - !form.controls.startDate.untouched - ) { -

Start date is required

- } -
+ imports: [JsonPipe, FormField, FormRoot], + templateUrl: './app.component.html', + changeDetection: ChangeDetectionStrategy.OnPush, +}) +export class AppComponent { + public isSubmitted = signal(false); -
- - - @if ( - form.controls.endDate.invalid && !form.controls.endDate.untouched - ) { -

- @if (form.controls.endDate.hasError('required')) { - End date is required - } @else if ( - form.controls.endDate.hasError('endDateBeforeStart') - ) { - End date must be after start date - } -

- } -
+ private readonly _initialData: RegistrationData = { + email: '', + password: '', + confirmPassword: '', + startDate: '', + endDate: '', + }; -
- - -
-
+ private _registrationModel = signal(this._initialData); + + protected registrationForm = form( + this._registrationModel, + (schemaPath) => { + required(schemaPath.email, { message: 'Email is required' }); + email(schemaPath.email, { + message: 'Please enter a valid email address', + }); + required(schemaPath.password, { message: 'Password is required' }); + minLength(schemaPath.password, 6, { + message: 'Password must be at least 6 characters', + }); + required(schemaPath.confirmPassword, { + message: 'Please confirm your password', + }); + validate(schemaPath.confirmPassword, ({ value, valueOf }) => { + const confirmPassword = value(); + const password = valueOf(schemaPath.password); + if (confirmPassword !== password) { + return { + kind: 'passwordMismatch', + message: 'Passwords do not match', + }; + } + return null; + }); + required(schemaPath.startDate, { message: 'Start date is required' }); + required(schemaPath.endDate, { message: 'End date is required' }); + validate(schemaPath.endDate, ({ value, valueOf }) => { + const startDate = valueOf(schemaPath.startDate); + const endDate = value(); + + if (!startDate || !endDate) { + return null; + } -
-

Form Status

-
-
- Valid: - - {{ form.valid ? 'Yes' : 'No' }} - -
-
- Touched: - {{ !form.untouched ? 'Yes' : 'No' }} -
-
- Dirty: - {{ form.dirty ? 'Yes' : 'No' }} -
-
-
-

Form Value:

-
{{ form.value | json }}
-
-
+ const start = new Date(startDate).getTime(); + const end = new Date(endDate).getTime(); - @if (isSubmitted()) { -
-

- Form Submitted Successfully! -

-
{{ this.form.getRawValue() | json }}
-
+ if (end < start) { + return { + kind: 'endDateBeforeStart', + message: 'End date must be after start date', + }; } -
-
- `, - changeDetection: ChangeDetectionStrategy.OnPush, -}) -export class AppComponent { - public isSubmitted = signal(false); - form = new FormGroup( - { - email: new FormControl('', [Validators.required, Validators.email]), - password: new FormControl('', [ - Validators.required, - Validators.minLength(6), - ]), - confirmPassword: new FormControl('', [Validators.required]), - startDate: new FormControl('', [Validators.required]), - endDate: new FormControl('', [Validators.required]), + return null; + }); }, { - validators: [passwordMatchValidator(), endDateAfterStartDateValidator()], + submission: { + action: async () => { + if (this.registrationForm().valid()) { + this.isSubmitted.set(true); + console.log('Form submitted:', this.registrationForm().value()); + } + }, + }, }, ); - // constructor() { - // this.form.controls.password.valueChanges - // .pipe(takeUntilDestroyed()) - // .subscribe(() => { - // this.form.controls.confirmPassword.updateValueAndValidity(); - // }); - // - // this.form.controls.startDate.valueChanges - // .pipe(takeUntilDestroyed()) - // .subscribe(() => { - // this.form.controls.endDate.updateValueAndValidity(); - // }); - // } - - onSubmit() { - console.log('Submitting form...', this.form); - if (this.form.valid) { - this.isSubmitted.set(true); - console.log('Form submitted:', this.form.getRawValue()); - } - } - - onReset() { - this.form.reset(); + public onReset(): void { + this.registrationForm().reset(this._initialData); this.isSubmitted.set(false); } }