Carl Rippon

Building SPAs

Carl Rippon
BlogBooks / CoursesAbout
This site uses cookies. Click here to find out more

Integrating Validation in Angular 2 and ASP.NET Core

February 10, 2017
dotnetangular

I’m building an Angular 2 app with an ASP.NET core web API behind it and need to add some validation. Obviously, I need to validate on the server in the web API, but I also want to do some client validation to make sure the user experience is good …

Server Validation

Here’s my model, which you can see is using attributes to define some required field validation:

public class Person
{
    public int PersonId { get; set; }
    [Required]    public string Title { get; set; }
    [Required]    public string FirstName { get; set; }
    [Required]    public string Surname { get; set; }
    [Required]    public string EmailAddress { get; set; }
}

Here’s our controller below.

If the required fields aren’t filled in (detected from the attribute validation) a HTTP 400 status code is returned with the validation errors in the response body.

We also do some more complex validation, checking if the email address is already in use. Again we return a 400 and the validation error if the email address exists in our database.

[HttpPost]
public IActionResult Post([FromBody]Person person)
{
    // return validation error if required fields aren't filled in
    if (!ModelState.IsValid)    {        return BadRequest(ModelState);    }
    // return validation error if email already exists
    Person existingPerson = peopleDataContext.People.Where(p => p.EmailAddress == person.EmailAddress).FirstOrDefault();    if (existingPerson != null)    {        ModelState.AddModelError("emailAddress", "This email already exists");        return BadRequest(ModelState);    }
    // validation has passed, so, save the person in the database
    peopleDataContext.People.Add(person);
    peopleDataContext.SaveChanges();

    // returned the saved person to the client
    return Json(person);
}

So, let’s test our API in Postman.

Test that required fields are validated:

ModelValidation

Test that a valid person is saved:

ValidPost

Test that a person with an email address already in the database is validated:

DupeEmail2

Wiring up our Angular 2 app

Now, that we have our web API in place, let’s build our page to submit a new person.

At this point we’re not going to implement any clientside validation - we’re just going to handle the validation coming from the server.

Here’s our component markup:

<div class="alert alert-danger" role="alert" *ngIf="errors.length > 0">
  <ul>
    <li *ngFor="let error of errors">
      {{ error }}
    </li>
  </ul>
</div>
<div class="alert alert-success" role="alert" *ngIf="successfulSave">
  Person saved successfully!
</div>

<form
  novalidate
  [formGroup]="personForm"
  (ngSubmit)="onSubmit()"
  class="form-horizontal"
>
  <div class="form-group">
    <div class="col-sm-4">
      <label class="control-label" for="title">Title</label><span>*</span>
      <select id="title" class="form-control" formControlName="title">
        <option></option>
        <option>Ms</option>
        <option>Mr</option>
        <option>Mrs</option>
        <option>Miss</option>
      </select>
    </div>
  </div>

  <div class="form-group">
    <div class="col-sm-6">
      <label class="control-label" for="firstName">First name</label
      ><span>*</span>
      <input
        type="text"
        class="form-control"
        id="firstName"
        formControlName="firstName"
      />
    </div>
  </div>

  <div class="form-group">
    <div class="col-sm-6">
      <label class="control-label" for="surname">Surname</label><span>*</span>
      <input
        type="text"
        class="form-control"
        id="surname"
        formControlName="surname"
      />
    </div>
  </div>

  <div class="form-group">
    <div class="col-sm-6">
      <label class="control-label" for="emailAddress">Email address</label
      ><span>*</span>
      <input
        type="email"
        class="form-control"
        id="emailAddress"
        formControlName="emailAddress"
      />
    </div>
  </div>

  <div class="form-group">
    <div class="col-sm-3">
      <button
        type="submit"
        class="btn btn-primary"
        [disabled]="personForm.invalid"
      >
        Submit
      </button>
    </div>
  </div>
</form>
  • Lines 1-7 will show the validation errors from the server
  • Lines 8-10 will show confirmation that the person has been saved into the database ok
  • Line 12 defines our form, telling angular that the form object will be called personForm and submit handler will be a function called onSubmit
  • Lines 14-46 define our field inputs for the person
  • Lines 48-52 defines our submit button

Here’s our component code:

import { Component, OnInit } from "@angular/core";
import {
  FormBuilder,
  FormGroup,
  FormControl,
  Validators
} from "@angular/forms";
import { Http, Response, Headers, RequestOptions } from "@angular/http";
import "rxjs/Rx";

@Component({
  selector: "person",
  template: require("./person.component.html")
})
export class PersonComponent implements OnInit {
  personForm: FormGroup;
  successfulSave: boolean;
  errors: string[];

  constructor(private fb: FormBuilder, private http: Http) {}

  ngOnInit() {
    this.personForm = this.fb.group({
      title: [""],
      firstName: [""],
      surname: [""],
      emailAddress: [""]
    });
    this.errors = [];
  }

  onSubmit() {
    if (this.personForm.valid) {
      let headers = new Headers({ "Content-Type": "application/json" });
      let options = new RequestOptions({ headers: headers });
      let person = {
        title: this.personForm.value.title,
        firstName: this.personForm.value.firstName,
        surname: this.personForm.value.surname,
        emailAddress: this.personForm.value.emailAddress
      };
      this.errors = [];
      this.http
        .post("/api/person", JSON.stringify(person), options)
        .map(res => res.json())
        .subscribe(
          data => (this.successfulSave = true),
          err => {
            this.successfulSave = false;
            if (err.status === 400) {
              // handle validation error
              let validationErrorDictionary = JSON.parse(err.text());
              for (var fieldName in validationErrorDictionary) {
                if (validationErrorDictionary.hasOwnProperty(fieldName)) {
                  this.errors.push(validationErrorDictionary[fieldName]);
                }
              }
            } else {
              this.errors.push("something went wrong!");
            }
          }
        );
    }
  }
}
  • Line 14 defines a variable called errors which will be an array of validation errors
  • ngOnInit on line 19 creates our form object with no client side validation defined at the moment
  • onSubmit posts the person object to our web API. You can see we catch validation errors on lines 47-55, pushing them to the errors array

If you run the app and hit the Submit button without filling in the inputs, you get something like:

validationsummaryerrors

Client Validation

Now, let’s implement the client validation in the angular app.

First, we need to change ngOnInit to include the validation rules:

ngOnInit() {
    this.personForm = this.fb.group({
        title: ['', Validators.required],        firstName: ['', Validators.required],        surname: ['', Validators.required],        emailAddress: ['', Validators.required]    });
    this.errors = [];
}

Then, we need to change our markup for the field inputs to output the validation errors. Here’s the markup for the title input:

<div
  class="form-group"
  [ngClass]="{'has-error':!personForm.controls.title.valid && personForm.controls.title.touched}">
  <div class="col-sm-4">
    <label class="control-label" for="title">Title</label><span>*</span>
    <select id="title" class="form-control" formControlName="title">
      <option></option>
      <option>Ms</option>
      <option>Mr</option>
      <option>Mrs</option>
      <option>Miss</option>
    </select>
  </div>
</div>
<div  class="alert alert-danger"  role="alert"  *ngIf="!personForm.controls.title.valid && personForm.controls.title.touched">  You must enter a title</div>
  • Line 1 adds the has-error CSS class if we have touched and not filled in the input
  • Lines 13-16 displays the error message beneath the input if we have touched and not filled in the input

So, cool, if the user doesn’t fill in any of the inputs, validation errors will be shown before the submit button is pressed: validationclient

However, what if we enter a duplicate email address:

As you can see, the validation error from the server is shown which is good, but ideally we want to highlight the email input and display the error beneath it. So, we need to integrate our validation errors from the server into Angular’s validation.

The solution is in the highlighted lines below. We know the field name from the server’s validation error dictionary. So, if the field is on our form, we flag to angular that there is an error using the control’s setErrors function.

In our example, we don’t have any validation that is not bound to a single field (cross field validation), but if we did, the validation errors would be shown at the top of the page.

onSubmit() {
    if (this.personForm.valid) {
        let headers = new Headers({ 'Content-Type': 'application/json' });
        let options = new RequestOptions({ headers: headers });
        let person = {
            title: this.personForm.value.title,
            firstName: this.personForm.value.firstName,
            surname: this.personForm.value.surname,
            emailAddress: this.personForm.value.emailAddress
        };
        this.errors = [];
        this.http.post('/api/person', JSON.stringify(person), options)
            .map(res => res.json())
            .subscribe(
                (data) => this.successfulSave = true,
                (err) => {
                    this.successfulSave = false;
                    if (err.status === 400) {
                        // handle validation error
                        let validationErrorDictionary = JSON.parse(err.text());
                        for (var fieldName in validationErrorDictionary) {
                            if (validationErrorDictionary.hasOwnProperty(fieldName)) {
                                if (this.personForm.controls[fieldName]) {                                    // integrate into angular's validation if we have field validation                                    this.personForm.controls[fieldName].setErrors({ invalid: true });                                } else {                                    // if we have cross field validation then show the validation error at the top of the screen                                    this.errors.push(validationErrorDictionary[fieldName]);                                }                            }
                        }
                    } else {
                        this.errors.push("something went wrong!");
                    }
                });
    }
}

Now, if we try to input a duplicate email, our user experience is much nicer:

validationintegrated2

If you to learn about using React with ASP.NET Core you might find my book useful:

ASP.NET Core 5 and React

ASP.NET Core 5 and React
Find out more

Want more content like this?

Subscribe to receive notifications on new blog posts and courses

Required
© Carl Rippon
Privacy Policy