Application Insights is Microsoft’s flavor of what’s known as an Application Performance Monitor, or APM for short. An APM is a tool that provides insights to developers and operations (DevOps) teams into how well an application and its underlying infrastructure is performing. In general, an APM is much different than something like Google Analytics. For all intents and purposes, Google Analytics is geared more towards capturing, converting, and retaining site visitors–essentially it’s a marketing tool. Application Insights, on the other hand, is a functional operations tool designed for developers and site reliability engineers.

In the previous posts, we explored two ways to add Google Analytics to an application: bundling an external JavaScript file and as a service. Those same approaches can be utilized for adding Application Insights to your Angular application. However, given the nature of Application Insights, we’re going to go a little bit further into development on this post. We’re going to build in some tracing that provides some better insights into how the user is interacting with our application.

All source code for this post can be found in my demo GitHub repository. You may it useful for easy reference. Additionally, this post assumes you understand how to use the Angular CLI.

Setup

Let’s begin by creating a few things:

  1. A new Angular application
    The demo application is using CLI version 8.3.29, but you can use whichever version you prefer. I’ve also chosen to use SCSS for my styles, but, again, choose what you prefer. Finally, make sure you enable routing. Enabling routing will help with requests and can be utilized for things like setting the page titles through routing properties.
  2. Create a few pages
    For the demo application, I’ve created 3 pages–Home, First Page, and Second Page. Additionally, on my first and second pages, I’ve added some buttons that bind to click events. This will allows us to interact with Application Insights API for tracking customer interactions.
  3. Create an Application Insights instance in Azure.

Application Insights

This first blog post will focus on creating a very quick and simple implementation of Application Insights. This implementation will be good enough for an initial deployment to production environments, but you will want to follow some better patterns and practices for capturing a good healthy amount of data to adequately troubleshoot and assess your application.

When creating your Application Insights instance in Azure, you will be provided an instrumentation key. Go ahead and copy this key. (NOTE: The instrumentation key will be different for your application. I’ve kept mine in the demo code in the source for your reference, but you will need to replace my key with yours.)

application insights instrumentation key

Saving the Tracking Code

When using Application Insights, our code is going to need to accomplish two things:

  1. Initialize Application Insights in the application
  2. Register a page view

Any other events within the application will also need to have the ability to log themselves and any other relevant data.

You’ll first want to create a new service using the Azure CLI (e.g. ng g s application-insights–this code tells the CLI to generate a new service called application-insights).

In the newly created service file (application-insights.service.ts should be located in your application’s src folder), paste the following code:

import { Injectable } from '@angular/core';
import { Router, NavigationEnd, ActivatedRoute } from '@angular/router';
import { filter } from 'rxjs/operators';
import { ApplicationInsights } from '@microsoft/applicationinsights-web';
import { environment } from 'src/environments/environment';

@Injectable({
  providedIn: 'root'
})
export class ApplicationInsightsService {
  appInsights: ApplicationInsights;

  constructor(private _router: Router, private _activatedRoute: ActivatedRoute) {
    this.appInsights = new ApplicationInsights({
      config: {
        instrumentationKey: environment.applicationInsightsKey,
        enableAutoRouteTracking: true
      }
    });
    this.appInsights.loadAppInsights();

    this._router.events.pipe(
      filter(event => event instanceof NavigationEnd)
    ).subscribe((page: NavigationEnd) => {
      const lastChild = child => child.firstChild ? lastChild(child.firstChild) : child;
      this.logPageView(lastChild(this._activatedRoute.firstChild).snapshot.data.title, page.url);
    });
  }

  init() { }

  logPageView(name, url) { // option to call manually
    this.appInsights.trackPageView({
      name: name,
      uri: url
    });
  }

  logEvent(name: string, properties?: { [key: string]: any }) {
    this.appInsights.trackEvent({ name: name }, properties);
  }

  logMetric(name: string, average: number, properties?: { [key: string]: any }) {
    this.appInsights.trackMetric({ name: name, average: average }, properties);
  }

  logException(exception: Error, severityLevel?: number) {
    this.appInsights.trackException({ exception: exception, severityLevel: severityLevel });
  }

  logTrace(message: string, properties?: { [key: string]: any }) {
    this.appInsights.trackTrace({ message: message }, properties);
  }
}

The above code does a few things.

  1. We’re creating an Angular service that will give our application a centralized point for interacting with Application Insights. This alleviates the need to add this code to every page component.
  2. In the constructor, we initialize Application Insights and we provide the instrumentation key (e.g. environment.applicationInsightsKey) from the environment’s configuration (line 16). More on this in the next section. The enableAutoRouteTracking: true configuration option is supposed to automatically detect page changes in single-page applications (SPA), but I’ve had very little luck with it auto-tracking page changes.
  3. We then load Application Insights (line 20).

Now, at this point we could simply call this.appInsights.trackPageView() (after this.appInsights.loadAppInsights(), line 20) and the user/session/pageview would be established. But, the logged page view would simply show Application Insights Service in the Application Insights Session dashboard. This doesn’t help us very much to tell us what page the user is viewing. Instead, we want to know the page title.

So we’re going to tie into the router events.

  1. We’re only interested when the router’s navigation process ends (e.g. NavigationEnd) so we’re going to filter for those events only and then subscribe to them.
  2. We don’t want to make the assumption that our component that is being loaded is always a top page. If we have nested areas within our application, we need to make sure the router “drills down” into the lowest level. Therefore, we recurse until we find the last child in the hierarchy (line 25).
  3. Once we find the last child, we call our service’s logPageView method (line 32) and pass the name and URL of our page. The name (or title) of the page will come from our router.

The remaining methods of the service can be used throughout our application as needed.

The benefit of this approach is that, unless we need any of these methods within a component, we don’t need to inject the service into that specific component. We will simply inject the service into our app.component.ts and that will be sufficient to track all page views across our application.

Environment Variable

If you think about it, we don’t necessarily want to track page views and interactions while the application is under development (local developer machines, shared development, etc.) (NOTE: It may be advantageous to track performance in QA and UAT environments to mitigate any bugs prior to promoting you code to a production environment.) But for simplicity’s sake, in this case, we only want to track performance when the application is pushed to production. The Angular CLI allows us to create separate environment variable values for different environments.

If you navigate to your application’s src/environments/ folder, you’ll see two files (e.g. environment.prod.ts and environment.ts). You may have more depending on the current development status of your application, but these are the two initial configurations. The prod file is used for production, whereas the other file is used for all other environments. In these files, you can specify different values for environment variables depending on the environment/configuration you are targeting. The only rule of thumb is that, while the values may be different, the keys in both files must be identical.

Given that, in your environment.prod.ts file, add the key/value from line 3 of the following code block (replace the Application Insights key with your own instrumentation key):

export const environment = {
  production: true,
  applicationInsightsKey: '979a730b-adef-4993-9cc6-f99f4ca7b1f4'
};

In your environment.ts file, simply add the key with an empty value:

export const environment = {
  production: false,
  applicationInsightsKey: ''
};

Now, when you build your application, depending on the configuration you specify, that configuration’s value for applicationInsightsKey will be inserted into your ApplicationInsightsService. If you specify a development-type build (i.e. non-production), then an empty string will be provided and, therefore, you will not have to worry about Application Insights tracking page interactions and events wile your application is being developed and/or tested. On the other hand, if you specify the production build, your Application Insights instrumentation key will properly be inserted and user interaction will be successfully captured.

You can see and compare the two environment files here.

Routing

In our application-insights.service.ts above, we subscribe to the router’s NavigationEnd event. Inside of our subscription, we attempt to capture the page’s title so that our Application Insights information is a little bit more informative. The page title information comes from our route data. You may have more or less or different pages within your application. But, for this post (and what you’ll find in the repository), there are three pages–Home, First Page, and Second Page. Therefore, my src/app/app-routing.module.ts looks like the following:

import { NgModule } from '@angular/core';
import { Routes, RouterModule } from '@angular/router';

import { HomePageComponent } from './home-page/home-page.component';
import { FirstPageComponent } from './first-page/first-page.component';
import { SecondPageComponent } from './second-page/second-page.component';

const routes: Routes = [
  { path: '', component: HomePageComponent, data: { title: 'Home' }},
  { path: 'first-page', component: FirstPageComponent, data: { title: 'First Page' }},
  { path: 'second-page', component: SecondPageComponent, data: { title: 'Second Page' }}
];

@NgModule({
  imports: [RouterModule.forRoot(routes)],
  exports: [RouterModule]
})
export class AppRoutingModule { }

Notice the data object on each of the routes. The object has a title property that contains the page’s title. This title is what our Application Insights service will read.

We are now ready to move forward and use our service.

ApplicationInsightsService Provider

Again, we’ve created a service and added the necessary code for tracking various telemetry, but before we can use it, we must inform Angular’s IoC container that the service is available for use.

In your application’s app/src/ folder, open the file app.module.ts and add the ApplicationInsightsService as a provider. Your code should look similar to the following snippet (see the repository for the full example).

import { BrowserModule } from '@angular/platform-browser';
import { NgModule } from '@angular/core';

import { AppRoutingModule } from './app-routing.module';
import { AppComponent } from './app.component';

import { ApplicationInsightsService } from './application-insights.service';

@NgModule({
  declarations: [
    AppComponent
  ],
  imports: [
    BrowserModule,
    AppRoutingModule
  ],
  providers: [ApplicationInsightsService],
  bootstrap: [AppComponent]
})
export class AppModule { }

Now, the service can be injected on each of your Angular application pages.

Initializing the Service

Before the page will begin tracking, the service must be initialized. We can do this from our app.component.ts by simply injecting the service into the application component’s constructor.

import { Component } from '@angular/core';
import { ApplicationInsightsService } from './application-insights.service';

@Component({
  selector: 'app-root',
  templateUrl: './app.component.html',
  styleUrls: ['./app.component.scss']
})
export class AppComponent implements OnInit {
  title = 'Application Insights Service';

  constructor(private _$aiService: ApplicationInsightsService) {  }

  ngOnInit() {
  }
}

The above code is all that is needed to load the Application Insights service. When the service is injected, the service’s constructor is called and, thus, Application Insights is initialized and the page view is logged.

NOTE: This code is temporary as we’ll move it in a bit. But, this is the simplest way to add Application Insights to all of your application’s pages.

Testing

At this point, your application should be ready for testing page views.

While under development, you are typically viewing/testing application changes by executing the command ng serve. However, remember, the Application Insights instrumentation id is stored in the environment.prod.ts file, which is only included when you specify the production build configuration. Therefore, to test the Application Insights integration in your development environment, you will need to include the --prod flag (e.g. ng serve --prod).

Once the application is running (with the production environment configuration loaded), begin navigating throughout your pages while checking the Sessions blade in your Application Insights instance. You should see your page navigation taking place.

Now that Application Insights has successfully been added to the application, you may want to track some basic user interactions. Continue to the next page to learn how.