Table of Contents
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 id
s 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.