AngularJS: API Error codes handling in Forms

TL;DR

Szymon Kosno
7 min readMay 17, 2015

Do not handle error codes in controller. Use directive to handle API errors as you would use standard Angular validators:

http://plnkr.co/edit/19cZ1WYRKOvZlSVSaDXb?p=preview

Lately I have been creating a lot of forms — administration applications just tend to have plenty of them. Obviously one of the most important topics I needed to solve is how to efficiently handle whatever server has to say about my beautiful model that I am sending to that beast.

Simple API error handling with global error and field error visible

Angular is great with forms having all those custom directives to handle validation and ng-messages to show errors as they come in (although lately I personally use custom directive based on ng-messages). But at some point you need to send all of that data to the server and a lot of bad things can happen. Depending on your application you can have 100% of server side validation replicated on Frontend, which is great, but often there are some checks that will be done just when you send the whole thing. Whatever is the case, your API will eventually send you a “not 200" response that you need to handle. More importantly, you need to give user feedback what happened.

Requirements

When planning for api error handling I decided that I need to cover several use cases:

  1. I need to show general api errors in given place in form (e.g. top of the form). This is when something happens and we don’t really know what (server failed)
  2. I need to show field related error next to the field as I would show standard validation error.
  3. I need to show some errors in general area that are field related but that field is not found in form.
  4. I want to be able to decide if particular form field should have error attached to that field or should it fall into “general error” area.
Use Case 1 and Use Case 2 for simple API error handling

I believe 1 and 2 are easy to understand but some explanation is needed for number 3 and 4.

Why number 3? Sometimes you are adding some data to request that is not explicitly editable by user inside form (hidden fields, some generated data). It can happen that this additional data is invalid. In such case we need to tell something meaningful to the user. Even if it is to contact administrator etc.

Use Case 3 and Use Case 4 for simple error handling

Why 4? This might be my custom requirement but I needed to be able to decide if particular form field is considered an “API field” and should get all errors delivered to that field $error. This solution assumes that field need to be consciously declared as “aware of api errors” to get those delivered.

Form validation and API errors

Frontend validators are cool. We know what happens, we know how to fix it. Angular comes in and gives us all those helpers like $valid, $invalid etc. But it is not so easy when handling API error. We do not necessarily know what is the reason error occurred just from response and we are not prepared to call any validation mechanism that could tell us when it got fixed. Error happened, user should read the message and act accordingly. We will just try not to get in user’s way.

To enable that we definitely do not want to block re-sending the form if we cannot say when user fixed issue. For that reason let’s divide API errors into 2 groups:

  1. Do not invalidate form at all (save button should be enabled)— use cases 1, 2 and 4
  2. Invalidate one field model (save button gets disabled)— use case 3

What it means is that if there is any type of general error not connected to any form field or the field is not found in form or not marked as “API error aware”, do not invalidate anything. If there is an API error for existing field and that field tells us that it wants to handle API errors, then apply that error to the field resulting in $invalid state for field model.

Direct result of those requirements is the need to implement proper mechanism to take in API error connected to the field and then remove it as user attempts to fix this error.

Planning solution

If you create a simple AngularJS application you might have created a controller that exposes function like “ctrl.save()” that sends the data and possibly handles error in promise failure block like:

This works ok for small applications but we need something more complex and a bit more automated and obvious from HTML perspective. For this we create following components:

  1. api-form directive — it should be declared next to “form” or “ng-form” and will automatically attach logic to that form controller.
  2. api-validate directive — it should be declared next to ng-model on particular field to indicate that this field is “server error aware”. Plus it will hold logic on when to remove error. It will expose “$registerApiError” interface on ng-model to handle API errors related with the field.
  3. ApiFormValidationService — service used to implement all the logic needed by this solution.

The goal is that by just adding “api-form” to any of our forms it will automatically start handling errors for us:

<form name="myForm" api-form>
<input type="text" name="fieldName" ng-model="fieldModel"/>
</form>

To have an error attached to the field errors, we just add api-validate directive next to it’s ng-model:

<form name="myForm" api-form>
<input type="text" name="fieldName" ng-model="fieldModel" api-validate/>
</form>

Of course we need to be able to show general errors for my form so I need to extend formController with some custom parameters — we will add $api object to it. This object will hold basic information similar to the form itself but exposing what backend knows about this form. All fields in this object should work similarly how you expect them to work in standard form:

$api: {
$valid: true,
$invalid: false,
$error: null
}

For the sake of this post let’s assume that backend is responding with this kind of structures when having error:

Implementation

Now that the general idea is there, it is time to prepare some the actual code to do the job.

API form validation service

First we will implement the service that will abstract most of the code responsible for parsing errors, applying them to form and field error handlers.

In the most basic form we expect service to expose:

  • reset(form) — simply reset current form “$api” object to it’s initial state;
  • setInvalid(form) — set $api form state to invalid form state; used when there is an API error not handled by field;
  • setValid(form) — set $api form state to valid form state; can be used to force API state to “valid” state;
  • applyError(form, error) — apply single error to given form; it checks if form field exist and has special “$registerApiError” interface exposed by field to know if error handling can be done by field itself;
  • applyErrors(form, reason) — parse API rejection reason and apply errors to form; used to handle rejected API communication;

Taking that into account we can create a factory service:

API validate directive

This validation mechanism requires that there can be a special end point “$registerApiError” exposed on field’s ng-model so that individual field error can be passed there and handled by user interacting with the field directly. This kind of handler enables us to show API error next to the field itself similarly as other UI validators.

Once user interacts with the field model we want to get rid of any API error related with that field. We do not really know when user fixes error properly so we provide just a “dumb” functionality to remove all API errors once validation loop kicks in for the field.

Our field validation directive could look something like this:

API form directive

Final piece required for this mechanism. This is the part that connects our service and field directive to provide full functionality. It should be added to “form” or “ng-form” HTML element.

For this directive to work, we need to know when form got submitted and more importantly when the request got resolved or rejected. In this post I will simply require that request promise is given directly as “api-form” input that we can watch. We might want to later on try include automatic form submission capture if needed.

How we want this directive to work is:

  1. Check if there is “form” or “ngForm” controller defined on element;
  2. Watch “api-form” attribute for value changes, once change detected try to handle it as request promise;
  3. Register request success handler, resetting form API state;
  4. Register request failure handler, forwarding handling and error application to API form service;

Code implementation could look something like that:

Conclusion

With those three pieces in place we can now simply use this mechanism to discard custom Controller error handling code and just pass promise that is returned by our request.

New Controller could look something like that:

Using it as directive:

And inside HTML:

What about additional data returned with request or full validation messages inside response? Hopefully in next part.

Live demo

What is a blog post without a demo? So here you are, hope you enjoyed!

http://plnkr.co/edit/19cZ1WYRKOvZlSVSaDXb?p=preview

--

--

Szymon Kosno

Technical Program Manager at Google, Developer most of the time unless trying to meditate