Exception Handling in an AngularJS Web Application (Tutorial)

Posted by Miguel Ángel Domínguez Coloma on Jun 3, 2014

Load Impact is one of the world's most used load testing SaaS platforms, but we know engineers have more interests than just performance. So, here's a tutorial from one of our very talented engineering friends.

During this tutorial I will implement best practices for exception handling in an AngularJS web application.

I'll start creating the structure of a demo application, throw some test exceptions and intercept them.The second part of this tutorial will cover some frameworks that will help you organize your exception logs using Raven with Sentry.


The leading load testing platform for developers

Start free plan

5 tests per month with 100 concurrent users


Creating web app structure

Note: You can skip this section if you already know how to setup your web application with Yeoman and AngularJS.

In order to use Yeoman to create a basic structure of your web app with AngularJS, you need to have installed Yeoman on your computer. A basic tutorial to install it can be found here: http://yeoman.io/gettingstarted.html

Once installed, run the following command and follow the instructions. When asked to install SASS, reply “N”. Rest of settings/questions can be set as default.

$ mkdir exceptionHandling
$ cd exceptionHandling
$ yo angular

The command will create a structure where you can find the structure of your web app and other files to configure it with Grunt and Bower (for more information, check http://gruntjs.com/ and http://bower.io/).

Now you are ready to execute your AngularJS application. Open a terminal and navigate to the root of your application. Run the following command to keep the web application running in the background:

$ grunt serve

Now, open your browser with the following url:

http://localhost:9001


Looking to load test your Postman collections?


Exceptions and Promises

Exceptions

In order to keep a good structure of your exceptions, create an object for every throw exception. The structure of this thrown object should look like this (this is just an example):

function MyException(message) {
  this.name = 'MyException';
  this.message= message;
}
MyException.prototype = new Error();
MyException.prototype.constructor = MyException;

Then, you can throw the exceptions as error objects:

throw new MyException('Something was wrong!');

Promises

Promise is an interface class that helps you to control if the call to a function - that may be executed asynchronously - has finished successfully or with an error. It is very similar to have a try-catch-finally block for async calls.

This is just an example:

function myAsyncFunction() {
  var deferred = $q.defer();
  // Doing something async...
  setTimeout(function() {

    // Notify AngularJS model about changes
    scope.$apply(function() {
      deferred.notify('Async method has ended.');

      /* This is just a random boolean value for the demo */
      var randomResult = (Math.random(2).toFixed(1) * 10) % 2;

      if (randomResult) {
        deferred.resolve('OK');
      } else {
        deferred.reject('FAIL');
      }
    });
  }, 1000);
  return deferred;
}

Then, you can call to your async function obtaining the results with the promise interface:

myAsyncFunction()
  .then(successCallback)
  .catch (errorCallback)
  .finally(alwaysCallback);

Throwing some exceptions

Now add to your controller a function that raises an exception. Open the file under the path app/scripts/controllers/main.js and write this content:

'use strict';

angular.module('exceptionHandlingApp')
  .controller('MainCtrl', ['$scope', '$q',
    function($scope, $q) {
      function MainCtrlInitException(message) {
        this.name = 'MainCtrlInitException';
        this.message = message;
      }
      MainCtrlInitException.prototype = new Error();
      MainCtrlInitException.prototype.constructor = MainCtrlInitException;

      function init() {
        /* We just reject the promise of this function. Only for the demo */
        return $q.reject("Cannot init");
      }

      init().
      catch (
        function(cause) {
          throw new MainCtrlInitException(cause);
        });
    }
  ]);

As you can see in the example, in the function “init”, I reject the promise that will cause the catch interface to be called. Then, I throw a custom exception.

Opening the console of your browser, you should see the results of this exception.

Intercepting exceptions with Decorators

AngularJS has its own exception handler. You can override it with a Decorator that helps you to extend the functionality of an object in AngularJS.

You can create a new Decorator using Yeoman Angular Generator in the terminal applying the following command:

$ yo angular:decorator customExceptionHandler

This command will create a new file under the path: app/scripts/decorators/customExceptionHandlerDecorator.js

Replace its content with the following code:

'use strict';

angular.module('exceptionHandlingApp')
  .config(function($provide) {
    $provide.decorator('$exceptionHandler', ['$log', '$delegate',
      function($log, $delegate) {
        return function(exception, cause) {
          $log.debug('Default exception handler.');
          $delegate(exception, cause);
        };
      }
    ]);
  });

In your browser, you should see the new result or your exception handler:

Image-One-2

Intercepting exceptions with Sentry

Sentry is a system that helps you organize your exceptions using a web application. You can setup an account on getsentry.com or install it on your own server. You will need to create a new project and obtain the API key to interact with your application.

Image-2

Be sure that you configure your hosts file to point your localhost as another domain.

In windows, you can find this file under “%SYSTEMROOT%System32driversetchosts” or in Linux/Mac under “/etc/hosts”

For example, you can use:

127.0.0.1   example.com

And configure Sentry to accept calls from example.com

Image-3

In order to interact with Sentry, we will need to use RavenJS with Sentry. On the bottom of your app/index.html file, before the last body tag, add the following line:

<scriptsrc="//cdn.ravenjs.com/1.1.14/jquery,native/raven.min.js"></script>

Then, modify app/scripts/app.js using the API key obtained from Sentry:

/* global Raven:true */
'use strict';

angular
  .module('exceptionHandlingApp', [
    'ngCookies',
    'ngResource',
    'ngSanitize',
    'ngRoute'
  ])
  .config(function ($routeProvider) {
      Raven.config('https://7be...............491@app.getsentry.com/2...2', {
        logger: 'Error Handling Demo',
      }).install();

    $routeProvider
      .when('/', {
        templateUrl: 'views/main.html',
        controller: 'MainCtrl'
      })
      .otherwise({
        redirectTo: '/'
      });
  });
 .otherwise({
        redirectTo: '/'
      });
  });

You will create a Decorator that interacts with Sentry, sending the exceptions to that server. You can use Yeoman Angular Generator:

$ yo angular:decorator sentryExceptionHandler

And replace the content of the file under app/scripts/decorators/sentryExceptionHandlerDecorator.js

/* global Raven:true */
'use strict';

angular.module('exceptionHandlingApp')
  .config(function($provide) {
    $provide.decorator('$exceptionHandler', ['$log', '$delegate',
      function($log, $delegate) {
        return function(exception, cause) {
          $log.debug('Sentry exception handler.');
          Raven.captureException(exception);
          $delegate(exception, cause);
        };
      }
    ]);
  });

Congratulations! Now you will see your exceptions appear on Sentry:

Image-4

 

Image-5

 

References


The leading load testing platform for developers

Start free plan

5 tests per month with 100 concurrent users

Topics: Grunt, Sentry, AngularJS, github, Exceptions and Promises, Bower, exception handling, Yeoman Angular Genetator, OdeToCode, Yeoman, Raven, Blog

Posts by Topic

see all