Server Form Validation with Angular.js

Server Form Validation with Angular.js

Jan Dudulski

At Monterail, we’ve fallen in love with Angular.js. It focuses on data and state instead of how your DOM should look like and where all those ids and classnames are. Unfortunately, there are still fields with dragons which we need to discover and learn how to deal with. Like the server-side form validation.

Can't we just validate everything on the front-end?

Angular already provides some form validators — ng-required or ng-pattern to name just a few. You can also write your own directive to validate, e.g. whether a username is unique. However even if you cover every known scenario something can still go wrong. A race condition can occur. Or you can simply be unaware of an edge case that all of a sudden happens on the production back-end.

Solution

You probably don’t need to change anything on the server side. For Rails you can just use respond_with or simply return json with model.errors as respond_with does.

First of all, let’s display errors in the template:

<form name="form" ng-submit="save(model)" novalidate>
  <input type="text" name="username" ng-model="user.name" server-error />
  <div class="errors" ng-show="form.username.$dirty && form.username.$invalid">
    <span ng-show="form.username.$error.server">{{ errors.username }}</span>
  </div>
  <input type="submit" />
</form>

Let’s discuss this. We’re creating a form with HTML5 validation disabled – just to avoid messing up with the angular and built-in browser validations. Next, we have an input field which binds to a username and a mysterious server-error directive which I’ll cover in a moment.

The most important part happens in the div.errors. We’re displaying it only when the field has changed ($dirty) and Angular knows that it’s invalid ($invalid). You probably want to display a specific error message for every handled error (e.g. required, pattern, length etc). In our example we’re only checking for server errors (and displaying them).

What does the server-error directive do?

angular.module('app').directive 'serverError', ->
  {
    restrict: 'A'
    require: '?ngModel'
    link: (scope, element, attrs, ctrl) ->
      element.on 'change', ->
        scope.$apply ->
          ctrl.$setValidity('server', true)
  }

It simply waits for any changes on the input and when they happen it invalidates the server error. form.username.$error.server will be changed to true and Angular will hide the error for us.

And the last part — the controller:

angular.module('app').controller 'UserCtrl', ($scope, UserService) ->
  $scope.save = (user) ->
    $scope.errors = {} # clean up server errors 
    success = (result) ->
      # do whatever you need on success     error = (result) ->
      # server will return something like:       # { errors: { username: ["Must be unique"] } }       angular.forEach result.data.errors, (errors, field) ->
        # tell the form that field is invalid         $scope.form[field].$setValidity('server', false)
        # keep the error messages from the server         $scope.errors[field] = errors.join(', ')

    UserService.create(user).then(success, error)

Of course, it would be nice to handle model base errors, but this is something you can do on your own.

That’s it! Let me know what you think or if something else works better for you.

Jan Dudulski avatar
Jan Dudulski