Angular: Correct approach to render server side validation messages

Let's say I have this form in Angular:

<form [formGroup]="form">
    <md-input-container>
        <input mdInput formControlName="name" [(ngModel)]="dummy.name" name="name">
        <md-error *ngIf="form.controls.name.hasError('required')">This is required</md-error>
    </md-input-container>
    <md-input-container>
        <input mdInput formControlName="email" [(ngModel)]="dummy.email" name="email">
        <md-error *ngIf="form.controls.email.hasError('invalid_format')">The email is not valid</md-error>
    </md-input-container>
</form>

      

which is sent to the web service to save, update, etc. The web service should, of course, validate the input and return some validation messages in case of any problems. Let's say I POST a form and get these validation errors in a JSON dictionary:

{
    "name": [
        "Middle names are not allowed."
    ],
    "email": [
        "The email already exists."
    ]
}

      

It is not clear to me how angular is supposed to handle this situation. Most of the information around suggests writing a special validator that will perform asynchronous validation with the server, for example:

uniqueEmailValidator(control: Control): {[key: string]: any} {
    return new Promise (resolve => {
        let headers = new Headers();
        headers.append('Content-Type', 'application/json');
        this.http.get('http://webservice.com/email/unique/', {headers:headers})
            .map(res => res.json())
            .subscribe(data => {
                if(data != null) {
                    resolve({"duplicate": true})
                }
                else resolve(null);      
            })
        });
    });
}

      

This now means that I have to have a specific web service for every single piece of data that can exist in my form. While this might make sense in some specific cases (like when authenticating by email / username), I don't like the idea of ​​filling my web API with a bunch of services that will only do validations and then have to write validators in my angular.

A possible solution I figured out is to store the server error object in my component as an attribute, and then have an angular validator to check that a particular field has an error message:

public checkServerErrorMessage(control: FormControl):{[key: string]: boolean}{
    if("name" in this.serverErrors){
        return { serverValidationError: true };
    }
    return {};
}

      

Then forcibly update the validators using updateValueAndValidity or something. But I don't think this is correct. Is there an "angular" approach to deal with this situation?

+3


source to share


1 answer


Since I am getting some buzz and just in case someone is struggling with this, here is the solution I came up with.

Instead of using validators, I ended up using the setError method to manually trigger validation errors on the controls (which is what the validator basically does in my opinion).

So in my component, I will iterate over the server response to determine if there is an error in each field. I think you can define this in different ways and it depends on how your web service was designed. In my case, there is an error if the name of the control is present in the dictionary of errors returned by the server. If so, I mark the corresponding formula control as "serverValidationError" (or, however, you want to call it):

for(var key in serverResponse["errors"]) { 

    this.serverErrors[key] = serverResponse["errors"][key]
    this.form.controls[key].setErrors({serverValidationError: true});

}

      

"serverErrors" will store messages from the server to display in the template.



Then in the template I check for this error:

<form [formGroup]="form">
    <md-input-container>
        <input mdInput formControlName="name" [(ngModel)]="dummy.name" name="name">
        <md-error *ngIf="form.controls.name.hasError('serverValidationError')">{{ serverErrors["name"] }}</md-error>
    </md-input-container>
    <md-input-container>
        <input mdInput formControlName="email" [(ngModel)]="dummy.email" name="email">
        <md-error *ngIf="form.controls.email.hasError('serverValidationError')">{{ serverErrors["email"] }}</md-error>
    </md-input-container>
</form>

      

I don't know if this is the best solution, but at least this is the best I could come up with.

PS: I am writing all of this from memory, so the code is probably not 100% correct.

0


source







All Articles