How create a formly async validation for multiple fields with different api calls?

142 views Asked by At

I want to validate multiple formly fields with different api calls without creating a custom validator for each scenario. For example in the same form a call to api/bankaccount-check, api/username-check for the username field and the banknumber field.

1

There are 1 answers

0
Maarten Heideman On

So I'll created a validation implementation for formly for multiple fields. Where the form service is an depedency for the validation (set in the module providers). This because of the async calls. The validation has a debounce to prevent unnecessary calls to the server. The link of the call can be set in the json powered forms (see example). If te server replies an data.error that error will be used as validation message (I'll use ionic). Each new keypress makes a call after the debounce time. So there is a live feedback on the users input.

formly multi validation setup

I'll hope someone has benefit of this method. Suggestions for optimalisation are welcome.

import { formService } from "src/app/services/form/form.service";
import { debounceTime, switchMap, map, first, distinctUntilChanged } from "rxjs/operators";
import { of } from "rxjs";
import { FormControl } from "@angular/forms";
import { FieldTypeConfig } from "@ngx-formly/core";

export function apiValidationFunction(FS: formService) {
    return {
        validators: [
            {
                name: "apiValidation",
                validation: (control: FormControl, field: FieldTypeConfig ) => {

                    if (!control.value || !field?.templateOptions?.validation?.url) {
                        return of(null);
                    }
                    const url = field?.templateOptions?.validation?.url;

                    return control.valueChanges.pipe(
                        debounceTime(field?.templateOptions?.validation?.debounce || 1000), // Adjust the debounce time as needed
                        distinctUntilChanged(),
                        switchMap((value) => FS.validationCall(url + value)),
                        map((result) => {
                            control.markAsTouched();
                            if (result && result["data"]["error"] !== "") {
                                control.setErrors({ apiValidation: {
                                    message: result["data"]["error"]
                                } });
                                return false;
                            } else {
                                return true;
                            }
                        }),
                        first((result) => result === true),
                    );
                },
            },
        ]
    };
}

The form module looks like this.

import { NgModule } from "@angular/core";
import { CommonModule } from "@angular/common";
import { IonicModule } from "@ionic/angular";
import { ReactiveFormsModule } from "@angular/forms";
import { AppFormlyConfig } from "./../../services/form/formly-config.service";
/** Formly */
import { FormlyModule, FormlyConfig,FORMLY_CONFIG} from "@ngx-formly/core";
import { FormlyIonicModule } from "@ngx-formly/ionic";
/** Validation */
import{ apiValidationFunction } from "./validators/api.validator";
import { FormService } from "src/app/services/form/form.service";

@NgModule({
    imports: [
        CommonModule,
        IonicModule,
        ReactiveFormsModule,
        FormlyModule.forRoot({
            validationMessages: [
                { name: "required", message: "Field is required" },
            ],
        }),
        FormlyIonicModule,
    ],
    declarations: [],
    providers: [
        { provide: FormlyConfig, useClass: AppFormlyConfig },
        {
            provide: FORMLY_CONFIG,
            multi: true,
            useFactory: apiValidationFunction,
            deps: [FormService],
        },
    ],
})
export class FormPageModule {}

To use this in a json powerd form you could use it like this:

'fields' => [
            [
                'key' => 'username',
                'type' => 'input',
                'templateOptions' => [
                    'label' => 'Username',
                    'required' => true,
                    'validation' => [
                        'url' => "https://example.com/api/username-check?username=",
                        'debounce' => 800,
                    ],
                ],
                "asyncValidators" => [
                    "validation" => [ 'apiValidation'],
                ]
            ],
            [
                'key' => 'bankaccount',
                'type' => 'input',
                'templateOptions' => [
                    'label' => 'Bankaccount',
                    'required' => true,
                    'validation' => [
                        'url' => "https://example.com/api/bankaccount-check?number=",
                        'debounce' => 800,
                    ],
                ],
                "asyncValidators" => [
                    "validation" => [ 'apiValidation'],
                ]
            ],
        ]

The form service is looking like this:

import { Injectable } from "@angular/core";
import { HttpHeaders, HttpClient } from "@angular/common/http";

export const HEADER = {
    headers: new HttpHeaders({
        "Content-Type": "undefined",
    }),
};

@Injectable({
    providedIn: "root",
})
export class FormService {
    passedData: any;

    constructor(private http: HttpClient) {}

    public validationCall(url: string) {
        return this.http.post(url, { headers: HEADER.headers })
    }
}