Adding Application Insights to an AngularJS Application

AngularJS is an amazing javascript based framework for creating rich Single Page Applications. Adding Microsoft Application Insights to your application can give you a large amount of data about how your users are utilizing the application.

Get an Application Insights Instrumentation Key

Before setting up your AngularJS application with Application Insights, you will need an instrumenation key. I have setup a quick guide on how to obtain an Application Insights Instrumentation Key, so follow that first if you need a key and return here afterwards. Please do not use an instrumentation key from a real application, as following the steps in this tutorial will send data to Application Insights and will clutter an existing application's telemetry with noise.

Setup the Demo Application

This guide utilizes a simple application as part of the walkthrough of adding Application Insights. In order to work along while reading, please follow these steps:
1. Install NodeJS if you don't already have it from nodejs.org
2. Clone the demo application's git repository:

> git clone https://github.com/khaines/angular-applicationinsights-demo.git

3. Install the starting depedencies

> cd angular-applicationinsight-demo
> npm install

4. Start the application.

 >npm start

You'll find the application running @ http://localhost:7890. Yes it is just an image carousel of kitten pictures, as it is a modified version of the carousel demo from the angular UI boostrap site. It makes for a good intro demo to adding Application Insights as it has some automated events, user initiated controls and log output. In this tutorial we will decorate this application to send various kinds of telemetry.

Adding Application Insights

For this tutorial we will be using the angular-applicationinsights module. This is different from the official Javascript SDK for Application Insights, as this module is a native AngularJS module that is designed for use on a Single Page Application.

Since the demo application is running on a nodejs server process, we can get the latest angular-applicationinsights module from npm:

> npm i angular-applicationinsights

This will place the module's js file in node_modules/angular-applicationinsights/build folder.

Next, let's add a reference to this downloaded module file. All of the files for the angularjs app are in the content/ folder of the git repository. Open index.html in your favorite editor and add the following line of markup to line 13:

    <script src="node_modules/angular-applicationinsights/build/angular-applicationinsights.min.js"></script>

The whole file should look like the example below at this point:

<!-- index.html -->  
<!DOCTYPE html>  
<html ng-app="myAppInsightsDemo">  
    <head>
      <base href="/">
      <!-- Latest compiled and minified CSS -->
      <link rel="stylesheet" href="https://maxcdn.bootstrapcdn.com/bootstrap/3.3.2/css/bootstrap.min.css">
      <!-- load angular and angular route via CDN -->
      <script src="https://ajax.googleapis.com/ajax/libs/angularjs/1.3.10/angular.js"></script>
      <script src="https://ajax.googleapis.com/ajax/libs/angularjs/1.3.10/angular-route.js"></script>

      <script src="ui-bootstrap-tpls-0.12.1.min.js"></script>
      <script src="node_modules/angular-applicationinsights/build/angular-applicationinsights.min.js"></script>

      <!-- Our controller code-->
      <script src="controllers.js"></script>

      <!-- Primary App Script-->
      <script src="app.js"></script>
    </head>
    <body>
        <!-- MAIN CONTENT AND INJECTED VIEWS -->
        <div id="main" class='container'>
            <div ng-view></div>
        </div>
    </body>
</html>  

If you save index.html, and reload the application in the browser, you should see the added js file being downloaded. Nothing else will happen because we need to tell the application to load the module. If you open app.js in the text editor, you can see which modules are being loaded by the application currently:

var app = angular.module('myAppInsightsDemo', ['ngRoute', 'controllers','ui.bootstrap']);  

Change that line to include the ApplicationInsightsModule, so it looks like this:

var app = angular.module('myAppInsightsDemo', ['ngRoute', 'controllers','ui.bootstrap', 'ApplicationInsightsModule']);  

Now that we have the Application Insights module declared, we need to configure it with the appropriate instrumentation key. You will need to include the applicationInsightsServiceProvider to the parameters of the app.config() call on line three,and then call applicationInsightsServiceProvider.configure() in this method. It should look something like this:

   app.config(function($routeProvider, $locationProvider, applicationInsightsServiceProvider) {

        applicationInsightsServiceProvider.configure('<YOUR INSTRUMENTATION KEY>', {appName:'appInsightsDemo'});

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

Note: Replace '<YOUR INSTRUMENTATION KEY>' with the instrumentation key you obtained from the azure portal.

The only option we've set is the appName, and all the other settings we've left as their default values. You can read about the various settings and their meanings in the configuration documentation on github.

Before you refresh the application page in your browser, make sure the development tools for the browser are open. This way you can see any network requests that happen for data going to Application Insights.

Once you do refresh your browser window for the application you should see a few HTTP POST requests go out to https://dc.services.visualstudio.com/v2/track. These are the default automatic instrumentation events for Application Insights, based on the application you have loaded. First, any view change which emits the $onLocationChangeSuccess event, will trigger a 'PageView' event to be sent to Application Insights. This mimics the page navigation events of traditional multi page applications.

Pageview Telemetry Request Sample

The second event that would be sent to Application Insights is a log message. On line 4 of controllers.js there is this line:

$log.info('main page loaded');

Any calls to angular's $log service will be automatically directed to Application Insights as well as the console. This can be toggled on/off in the module configuration, but having good log traces is vital to understanding issues that happen within an application.

These defaults are a good baseline to start with, but even a basic application needs some extra instrumentation to get a detailed understanding of users' behaviour.

Before we change anything in controllers.js, we need to make sure the application insights service available to the code we are modifying, so line 3 of controllers.js should be changed to look like this:

controllers.controller('mainController',['$scope','$log', 'applicationInsightsService', function($scope, $log, appInsights){  

Now, lets update a call to $log in the controllers.js that isn't providing much value in its current form. On line 7, there is a line that by itself that just outputs some data to the info log when ever the slide changes. While you can search and read trace logs in Application Insights, it might be more interesting to track the trends of which images are shown and what direction the carousel is moving. We can do this by tracking calls to the onSlideChanged function as a custom event instead of a trace log.

    $scope.onSlideChanged = function (nextSlide, direction) {
        appInsights.trackEvent('onSlideChanged', { imageId:nextSlide, direction:direction === undefined? 'first':direction });
    };
    // there is something in the carousel code that is calling this method twice every change, 
    // so this event gets duplicated. Once I find the cause I will update the sample application.

The first paramter of trackEvent is the name of the event, followed by hash object of properties we want to associate with this event. The undefined check on direction is because when the page is first loaded, there is no direction on the slides and the properties object will not track undefined values. If you refresh the page now, you will see the repeating log entries are replaced with events.

Tracking the number of extra images a user adds can also be useful. However we don't want to track the initial 4 images the application adds by default. So add a new variable and method to the controllers.js file around line 23:

    var extraSlides=0;
      $scope.addExtraSlide = function(){
          appInsights.trackMetric('Extra Slides Added', ++extraSlides);
          $scope.addSlide();
      };

Following all those edits to controllers.js we end up with a file looking like this:

var controllers = angular.module('controllers',[]);

controllers.controller('mainController',['$scope','$log', 'applicationInsightsService', function($scope, $log, appInsights){  
    $log.info('main page loaded');

    $scope.onSlideChanged = function (nextSlide, direction) {
        appInsights.trackEvent('onSlideChanged', { imageId:nextSlide, direction:direction === undefined? 'first':direction });
    };

          $scope.myInterval = 5000;
          var slides = $scope.slides = [];
          $scope.addSlide = function() {
        var newWidth = 600 + slides.length + 1;
            slides.push({
                  image: 'http://placekitten.com/' + newWidth + '/300',
                  text: ['More','Extra','Lots of','Surplus'][slides.length % 4] + ' ' +
                ['Cats', 'Kittys', 'Felines', 'Cutes'][slides.length % 4]
            });
          };
          for (var i=0; i<4; i++) {
            $scope.addSlide();
          }

      var extraSlides=0;
      $scope.addExtraSlide = function(){
          appInsights.trackMetric('Extra Slides Added', ++extraSlides);
          $scope.addSlide();
      };
}])

.directive('onCarouselChange', function ($parse) {
  return {
    require: 'carousel',
    link: function (scope, element, attrs, carouselCtrl) {
      var fn = $parse(attrs.onCarouselChange);
      var origSelect = carouselCtrl.select;
      carouselCtrl.select = function (nextSlide, direction) {
        if (nextSlide !== this.currentSlide) {
          fn(scope, {
            nextSlide: nextSlide,
            direction: direction,
          });
        }
        return origSelect.apply(this, arguments);
      };
    }
  };
});

The last edit is to the views/main.html file on line 15 so that the Add Image button calls this addExtraSlide function instead of the original addSlide function.

   <button type="button" class="btn btn-info" ng-click="addExtraSlide()">Add Slide</button>

One the page is refreshed, everytime you click on the Add Image button, a metric event will be sent to Application Insights. The first parameter of trackMetric is just like trackEvent, it is the name that will be shown in reports. The second parameter is the value of the metric, and must be a number.

The tutorial application in a completed state can also be downloaded by cloning the 'completed' branch of the git repository. You will need to add your own instrumentation key however.

The examples shown here are just scratching the surface of how you can use Application Insights in an angularJS application. You may want to visit the new Windows Azure Portal and have a look at the telemetry data that has been sent from the application you have instrumented during this tutorial.

For further reading, the full Application Insights for AngularJS reference documentation can be found on the github project page @ https://github.com/khaines/angular-applicationinsights/. There are also official videos and guides on using the azure portal reporting UI for Application Insights on MSDN.

I hope that this brief tutorial has demonstrated how simple it is to add Application Insights to an existing application. If you have any questions about how to use the module in your application, please do not hesitate to ask me.

comments powered by Disqus