Installing TrackJS in Angular 5+

“focus photography of black fly” by Robert Kresse on Unsplash

The bane of any software product is the dreaded Customer Bug Report™. As developers we go spelunking deep into the code armed with little more than “It doesn’t work”. In many cases we use the date of the error report to begin digging through the various system logs. Sometimes that’s helpful, but it really doesn’t do much when the issue is in the code that only runs in a browser.

As part of building the FlexePark app, my team invested a lot of energy in capturing every conceivable exception and error, and making sure the error got logged back to our service layer with as much context as possible. We even added exception tracking via Google Analytics for good measure. But researching errors was still tedious, and even determining which errors demanded attention was sometimes challenging.

So when I was at the Kansas City Developer Conference this summer I spent a lot of time talking with Todd H. Gardner and the folks with TrackJS. The ability to view client-side issues with 100% of that sweet, sweet user context had me sold. So while I was still at the conference I added a story to our Jira board: “Add TrackJS for error reporting”.

Yesterday was the lucky day when I got to snag that card! It all started with this tweet:


After signing up for TrackJS I was taken straight to the installation instructions, which look like this:

<!-- BEGIN TRACKJS -->
<script type="text/javascript">window._trackJs = { token: 'YOUR_TOKEN_HERE' };</script>
<script src="https://cdn.trackjs.com/releases/current/tracker.js"></script>
<!-- END TRACKJS -->

This is great for a POC and even some production web apps. But a lot of Angular 5+ apps (like ours) prefer to install libraries via NPM and avoid doing any setup and configuration directly within the index.html file. As I started down this road I realized that while there is a ton of great documentation on Build and Framework Integrations, there isn’t really anything specific to a full Angular Typescript integration.

After muddling through for a bit I learned a lot about how an Angular app loads and came up with an integration that seems to work well. This is what I have documented below.


It’s pretty easy to find the trackjs NPM package. Installation is simply:

$ npm install trackjs --save

Configuration took a bit longer. I started by adding a basic configuration to my environment.ts file:

export const environment = {
trackJs: {
token: '<get your own, thanks>',
enabled: true,
application: 'flexepark',
console: { display: true },
},
};

TrackJS supports multiple applications, so if you want to have multiple apps use your TrackJS account you can totally do this by setting up applications. (Our implementation is for FlexePark, as previously mentioned.) You can also use multiple application to support various environments (for example, we could use ‘flexepark-dev’).

Note that I’ve gone ahead and explicitly included both the enabled and console.display values since I’ll probably want these to change across environments (especially since I’m not using multiple applications right now).

Next up I need to be sure that TrackJS is loaded as early as possible when my Angular app is starting up. This is the part I couldn’t find documented anywhere and I honestly wasn’t sure where to start. Turns out, it’s pretty simple — add the necessary setup bits to your main.ts file:

import { enableProdMode } from '@angular/core';
import { platformBrowserDynamic } from '@angular/platform-browser-dynamic';
import { AppModule } from './app/app.module';
import { environment } from './environments/environment';
// Initialize TrackJS for error handling
window['_trackJs'] = environment.trackJs;
import 'trackjs';
if (environment.production) {
enableProdMode();
}
platformBrowserDynamic().bootstrapModule(AppModule)
.catch(err => console.log(err));

Note that we’ve added the configuration to window._trackJs just as the documentation says, but that we’ve pulled it in verbatim from the current environment configuration.

Believe it or not, that’s it! You now have TrackJS integrated into your Angular startup process. Everything is working!! (See the Quick Start guide for instructions on verifying this.)

But, we want to do a bit more …


One thing I really wanted to do is take advantage of the app version attribute that TrackJS supports. I also didn’t want to add yet another thing I need to remember to maintain manually, so I figured the version from the package.json file would work best.

We can add this directly into the configuration within the TrackJS section of our environment.ts file:

trackJs: {
version: require('../../package.json').version,
}

Unfortunately, if you try to do a build now you’ll get an error:

error TS2304: Cannot find name ‘require'

To solve this we need to do two things:

  1. Edit your src/tsconfig.app.json file to include "types": ["node"]
  2. Add @types/node to your dependencies. If it’s already in your package.json file under devDependencies you can simply move this entry to the dependencies section. Or if it’s not in the file at all you can install it with: $ npm install @types/node --save

Now when you run your build everything should work as expected, and performing a test on TrackJS should reveal that your app version is now being reported!


[UPDATE 8/31/2018] Everything is working now, but there’s a small problem. When you want to do a prod build such as ng build --prod you’ll discover a small error: “TrackJS could not find a token”.

Because we’ve already done the configuration to pull in the version number using require this becomes a simple update to the main.ts file:

// Initialize TrackJS for error handling
window['_trackJs'] = environment.trackJs;
import 'trackjs';

becomes:

// Initialize TrackJS for error handling
if (environment.trackJs) {
window['_trackJs'] = environment.trackJs;
require('trackjs');
}

Using require has two advantages over import in that (a) it doesn’t get rearranged during the build process (it’s critical that the _trackJs variable gets set before the import occurs), and (b) we can include it inside of another block.

So in the process of fixing the build issue we’ve also been able to add a defensive configuration block that simply avoids using TrackJS altogether if it’s not configured for a given environment.


There’s just one more big step in making sure you have a full Angular 5+ integration and that is setting up an ErrorHandler. This is well covered in the TrackJS framework integrations, but I added a little something special I wanted to share:

  1. I wanted to log the exception to Google Analytics
  2. I wanted to report the error to the user (since otherwise they wouldn’t know anything bad happened at all — getting here means we missed the error everywhere else)

I followed the TrackJS instructions, but since we already had an ErrorHandler called GlobalErrorHandler I added the necessary configuration here:

import { Injectable, ErrorHandler, Injector } from '@angular/core';
import { Angulartics2 } from 'angulartics2';
import { MatSnackBar } from '@angular/material/snack-bar';
declare var trackJs: any;
@Injectable()
export class GlobalErrorHandler implements ErrorHandler {
  constructor(
public injector: Injector
) { }
  handleError(error) {
// This can have useful content for TrackJS (and our developers)
console.warn(error.message);
    if (trackJs) {
// Send the native error object to TrackJS
trackJs.track(error.originalError || error);
}
    // Track the event in Google Analytics
const analytics = this.injector.get(Angulartics2);
analytics.exceptionTrack.next({
fatal: true,
description: error.message
});
    // Report error to the user
const snackBar = this.injector.get(MatSnackBar);
snackBar.open(error.message, 'Close');
}
}

The above error handler assume that you’re using Angulartics2 for your analytics integration and that you’re using the Angular Material library. If these assumptions are not true then you can easily substitute in your own integrations here.

The important bit is to be sure you use the injector to pull in external services instead of the constructor injection more commonly used by Angular services.


The only additional thing I did is add a post-load configuration to setup User tracking. You need to make the following call wherever you get notified about user login/logout actions:

trackJs.configure({ userId: user.id });

And that’s it! You now have a TrackJS implementation in Angular 5+ using a more “angular-y” implementation, and you are even tracking errors by application version! My initial implementation took 3 hours and was celebrated in this tweet:

Please let me know if you have any suggestions or corrections to anything you try here.

Good luck bug hunting.