From cc4bf76c13e38e9065d49161b6e0485657424577 Mon Sep 17 00:00:00 2001 From: Chocobozzz Date: Wed, 29 Dec 2021 15:33:24 +0100 Subject: Handle async validators --- .../shared/form-validators/form-validator.model.ts | 4 ++- .../src/app/shared/shared-forms/form-reactive.ts | 40 ++++++++++++++-------- .../shared/shared-forms/form-validator.service.ts | 16 +++++++-- .../remote-subscribe.component.ts | 2 +- 4 files changed, 44 insertions(+), 18 deletions(-) (limited to 'client/src/app/shared') diff --git a/client/src/app/shared/form-validators/form-validator.model.ts b/client/src/app/shared/form-validators/form-validator.model.ts index 6f2472ccd..31c253b9b 100644 --- a/client/src/app/shared/form-validators/form-validator.model.ts +++ b/client/src/app/shared/form-validators/form-validator.model.ts @@ -1,7 +1,9 @@ -import { ValidatorFn } from '@angular/forms' +import { AsyncValidatorFn, ValidatorFn } from '@angular/forms' export type BuildFormValidator = { VALIDATORS: ValidatorFn[] + ASYNC_VALIDATORS?: AsyncValidatorFn[] + MESSAGES: { [ name: string ]: string } } diff --git a/client/src/app/shared/shared-forms/form-reactive.ts b/client/src/app/shared/shared-forms/form-reactive.ts index 30b59c141..07a12c6f6 100644 --- a/client/src/app/shared/shared-forms/form-reactive.ts +++ b/client/src/app/shared/shared-forms/form-reactive.ts @@ -1,4 +1,6 @@ + import { FormGroup } from '@angular/forms' +import { wait } from '@root-helpers/utils' import { BuildFormArgument, BuildFormDefaultValues } from '../form-validators/form-validator.model' import { FormValidatorService } from './form-validator.service' @@ -22,30 +24,42 @@ export abstract class FormReactive { this.formErrors = formErrors this.validationMessages = validationMessages - this.form.valueChanges.subscribe(() => this.onValueChanged(this.form, this.formErrors, this.validationMessages, false)) + this.form.statusChanges.subscribe(async status => { + // FIXME: remove when https://github.com/angular/angular/issues/41519 is fixed + await this.waitPendingCheck() + + this.onStatusChanged(this.form, this.formErrors, this.validationMessages) + }) } - protected forceCheck () { - return this.onValueChanged(this.form, this.formErrors, this.validationMessages, true) + protected async waitPendingCheck () { + if (this.form.status !== 'PENDING') return + + // FIXME: the following line does not work: https://github.com/angular/angular/issues/41519 + // return firstValueFrom(this.form.statusChanges.pipe(filter(status => status !== 'PENDING'))) + // So we have to fallback to active wait :/ + + do { + await wait(10) + } while (this.form.status === 'PENDING') } - protected check () { - return this.onValueChanged(this.form, this.formErrors, this.validationMessages, false) + protected forceCheck () { + this.onStatusChanged(this.form, this.formErrors, this.validationMessages, false) } - private onValueChanged ( + private onStatusChanged ( form: FormGroup, formErrors: FormReactiveErrors, validationMessages: FormReactiveValidationMessages, - forceCheck = false + onlyDirty = true ) { for (const field of Object.keys(formErrors)) { if (formErrors[field] && typeof formErrors[field] === 'object') { - this.onValueChanged( + this.onStatusChanged( form.controls[field] as FormGroup, formErrors[field] as FormReactiveErrors, - validationMessages[field] as FormReactiveValidationMessages, - forceCheck + validationMessages[field] as FormReactiveValidationMessages ) continue } @@ -56,8 +70,7 @@ export abstract class FormReactive { if (control.dirty) this.formChanged = true - if (forceCheck) control.updateValueAndValidity({ emitEvent: false }) - if (!control || !control.dirty || !control.enabled || control.valid) continue + if (!control || (onlyDirty && !control.dirty) || !control.enabled || !control.errors) continue const staticMessages = validationMessages[field] for (const key of Object.keys(control.errors)) { @@ -65,11 +78,10 @@ export abstract class FormReactive { // Try to find error message in static validation messages first // Then check if the validator returns a string that is the error - if (typeof formErrorValue === 'boolean') formErrors[field] += staticMessages[key] + ' ' + if (staticMessages[key]) formErrors[field] += staticMessages[key] + ' ' else if (typeof formErrorValue === 'string') formErrors[field] += control.errors[key] else throw new Error('Form error value of ' + field + ' is invalid') } } } - } diff --git a/client/src/app/shared/shared-forms/form-validator.service.ts b/client/src/app/shared/shared-forms/form-validator.service.ts index 055fbb2d9..0fe50ac9b 100644 --- a/client/src/app/shared/shared-forms/form-validator.service.ts +++ b/client/src/app/shared/shared-forms/form-validator.service.ts @@ -1,5 +1,5 @@ import { Injectable } from '@angular/core' -import { FormBuilder, FormControl, FormGroup, ValidatorFn } from '@angular/forms' +import { AsyncValidatorFn, FormArray, FormBuilder, FormControl, FormGroup, ValidatorFn } from '@angular/forms' import { BuildFormArgument, BuildFormDefaultValues } from '../form-validators/form-validator.model' import { FormReactiveErrors, FormReactiveValidationMessages } from './form-reactive' @@ -68,11 +68,23 @@ export class FormValidatorService { form.addControl( name, - new FormControl(defaultValue, field?.VALIDATORS as ValidatorFn[]) + new FormControl(defaultValue, field?.VALIDATORS as ValidatorFn[], field?.ASYNC_VALIDATORS as AsyncValidatorFn[]) ) } } + updateTreeValidity (group: FormGroup | FormArray): void { + for (const key of Object.keys(group.controls)) { + const abstractControl = group.controls[key] as FormControl + + if (abstractControl instanceof FormGroup || abstractControl instanceof FormArray) { + this.updateTreeValidity(abstractControl) + } else { + abstractControl.updateValueAndValidity({ emitEvent: false }) + } + } + } + private isRecursiveField (field: any) { return field && typeof field === 'object' && !field.MESSAGES && !field.VALIDATORS } diff --git a/client/src/app/shared/shared-user-subscription/remote-subscribe.component.ts b/client/src/app/shared/shared-user-subscription/remote-subscribe.component.ts index a951134eb..369692715 100644 --- a/client/src/app/shared/shared-user-subscription/remote-subscribe.component.ts +++ b/client/src/app/shared/shared-user-subscription/remote-subscribe.component.ts @@ -27,7 +27,7 @@ export class RemoteSubscribeComponent extends FormReactive implements OnInit { } onValidKey () { - this.check() + this.forceCheck() if (!this.form.valid) return this.formValidated() -- cgit v1.2.3