DRYing up AngularJS’s Error Handling

At Liquid we make extensive use of AngularJS to improve our UI’s responsiveness and ease AJAX querying.

If you’re using it as well, this is probably a common sight:

$http(
method: <some-method>,
url: <some-url>
).success((data) ->

).error((data, status) ->
alertError(data, status)
)

In every single request you make to your server, you must warn your user when something goes wrong. That means adding a (often the same) line of code to any ajax request you make and, in cases where the error must be handled by the user, you’re forced to add an error handler just for the purpose of printing error messages.

This means a lot of duplicate code and a larger footprint for your app. This was a problem in Liquid, and here’s how we fixed it.

$httpProvider Interceptors

AngularJS provides a very handy feature through the $httpProvider called Interceptors. Somewhat similar to some AOP constructs, these interceptors can wrap an ajax call after it is sent and before it is received by your code.

Using interceptors you can change, reject, inspect, any ajax request your code makes before and after it hits the server. In our case, we’ll use them to show errors when they happen. So, onto our base interceptor:

app.factory(‘ajaxErrorsInterceptor’, [‘$q’, ($q) ->
{
request: (config) -> config
requestError: (rejection) -> $q.reject(rejection)
response: (response) -> response
responseError: (rejection) -> $q.reject(rejection)
}
])

This is the most basic (and useless) interceptor: it passes on successes and rejects errors. Basically a NoOp. Lets make it show error messages:

app.factory(‘ajaxErrorsInterceptor’, [‘$q’, ‘messagesService’, ($q, messagesService) ->
{

responseError: (rejection) ->
error = rejection.data
messagesService.addError(error.title, error.message, error.details)

}
])

We can assume there already is a service that shows messages to the user (in our case, at the top of the view) with a handy method for adding an error. We should make it easy for our rails back-end to send messages that we understand:

 def render_json_message(title, message, details, status)
render json: {
title: title,
message: message,
details: details.kind_of?(Array) ? details : [details]
}, status: status
end

Running this in any controller will send an error message that our interceptor understands. All we’re left to do is add the interceptor to the interceptor chain:

app.config([“$httpProvider”, ($httpProvider) ->
$httpProvider.interceptors.push(‘ajaxErrorsInterceptor’)
])

There. We’re now handling all error messages in a central, unique location of our code, and can now remove all of our old error message handlers.

Improvements

What if the same request is repeated? We don’t want the old error message to confuse our users, so we’d better remove it.

We start by saving a reference to each error message generated by the interceptor:

app.factory(‘ajaxErrorsInterceptor’, [‘$q’, ‘messagesService’, ($q, messagesService) ->
  errors = []
  putMessage = (config, message) ->
errors[config.method] ||= []
errors[config.method][config.url] = message

{

responseError: (rejection) ->
error = rejection.data
message = messagesService.addError(error.title, error.message, error.details)
putMessage(rejection.config, message)

}
])

We distinguish requests by method and url because different parameters might be what is necessary to make a request be successful.

Finally we listen for a successful response to any request, and remove any messages on the same route. The success message can be added by our success handler further down the chain.

popMessage = (config) ->
(errors[config.method] && errors[config.method][config.url]) || null
{

request: (config) ->
msg = popMessage(config)
messagesService.remove(msg) if msg
  config

}

And it’s gone! No message spam happens when the same request fails multiple time nor is the user confused by error messages when the request is successful.

Sum up

Other uses can be found and implemented for interceptors: Keep a count of each pending request and display a spinner so that the user knows when information is being synced; or append data to each request your app sends.

Here we made a simple message interceptor to show error messages to our users from a single point in our JS code. We extended it further so that the same message does not appear twice and doesn’t stick around when it becomes stale.

In total, we removed 47 calls to our message service and 38 error handling functions that served no other purpose than printing an error.

Liquid’s code is DRYer, shorter, simpler, and we have one less concern when performing ajax requests.

João Ferreira (@jmnsferreira)

Show your support

Clapping shows how much you appreciated João Ferreira’s story.