# Setting up Segment Analytics in Angular with environment files

## Introduction

I recently finished working on an Angular web app and it was time to set up analytics to better understand the user experience it delivers. My choice of analytics provider was [Segment](https://segment.com/) as it allows setting up a single analytics source within the app and feed the data to various other [destinations](https://segment.com/docs/connections/destinations/catalog/) like Google Analytics and Amplitude.

Having decided on the analytics service to use, I now wanted to integrate it within Angular and test it across different app environments. For the app, I had three environments - dev, staging and production. By specifying different analytics keys for each environment, it prevents skewing live analytics while allowing us to test our integration in a non-production environment.

## Including the Segment analytics tracking snippet

Setting up Segment starts with including their [tracking snippet](https://segment.com/docs/connections/sources/catalog/libraries/website/javascript/quickstart/#step-2-add-the-segment-snippet) in our `index.html`:

```javascript
<script>
  !function(){var analytics=window.analytics=window.analytics||[];if(!analytics.initialize)if(analytics.invoked)window.console&&console.error&&console.error("Segment snippet included twice.");else{analytics.invoked=!0;analytics.methods=["trackSubmit","trackClick","trackLink","trackForm","pageview","identify","reset","group","track","ready","alias","debug","page","once","off","on","addSourceMiddleware","addIntegrationMiddleware","setAnonymousId","addDestinationMiddleware"];analytics.factory=function(e){return function(){var t=Array.prototype.slice.call(arguments);t.unshift(e);analytics.push(t);return analytics}};for(var e=0;e<analytics.methods.length;e++){var key=analytics.methods[e];analytics[key]=analytics.factory(key)}analytics.load=function(key,e){var t=document.createElement("script");t.type="text/javascript";t.async=!0;t.src="https://cdn.segment.com/analytics.js/v1/" + key + "/analytics.min.js";var n=document.getElementsByTagName("script")[0];n.parentNode.insertBefore(t,n);analytics._loadOptions=e};analytics._writeKey="YOUR_WRITE_KEY";analytics.SNIPPET_VERSION="4.15.2";
  analytics.load("YOUR_WRITE_KEY");
  analytics.page();
  }}();
</script>
```

Note the following lines of code towards the end:
```javascript
  analytics._writeKey="YOUR_WRITE_KEY"
  analytics.load("YOUR_WRITE_KEY");
  analytics.page();
```

We'll remove these lines from the snippet and handle them ourselves in the Angular app, so the new tracking code we include in our `index.html` becomes:

```javascript
<script>
  !function(){var analytics=window.analytics=window.analytics||[];if(!analytics.initialize)if(analytics.invoked)window.console&&console.error&&console.error("Segment snippet included twice.");else{analytics.invoked=!0;analytics.methods=["trackSubmit","trackClick","trackLink","trackForm","pageview","identify","reset","group","track","ready","alias","debug","page","once","off","on","addSourceMiddleware","addIntegrationMiddleware","setAnonymousId","addDestinationMiddleware"];analytics.factory=function(e){return function(){var t=Array.prototype.slice.call(arguments);t.unshift(e);analytics.push(t);return analytics}};for(var e=0;e<analytics.methods.length;e++){var key=analytics.methods[e];analytics[key]=analytics.factory(key)}analytics.load=function(key,e){var t=document.createElement("script");t.type="text/javascript";t.async=!0;t.src="https://cdn.segment.com/analytics.js/v1/" + key + "/analytics.min.js";var n=document.getElementsByTagName("script")[0];n.parentNode.insertBefore(t,n);analytics._loadOptions=e};analytics.SNIPPET_VERSION="4.15.2";}}();
</script>
```
This allows us to share the "base" Segment tracking snippet in all app environments while specifying a `SEGMENT_WRITE_KEY` specific to each.

## Specifying a different key for each environment

Now, how do we go about specifying a key for each environment?

At the time of writing, when creating a new app with the Angular CLI using `ng new`, it creates two environment files in the `environments` folder by default:
1. `environment.ts`: environment variables for local development
2. `environment.prod.ts`: environment variables for the production build when running `ng build --configuration production` or the default `ng build` command

So, we can use these files to specify different analytics keys for Segment:

```javascript
// file environment.ts:

export const environment = {
  production: false,
  analytics: {
    segmentWriteKey: 'DEV_SEGMENT_WRITE_KEY'
  }
};
```

```javascript
// file environment.prod.ts:

export const environment = {
  production: true,
  analytics: {
    segmentWriteKey: 'PRODUCTION_SEGMENT_WRITE_KEY'
  }
};
```

Having specified the different keys, we need to initiate Segment analytics with the correct key. This can be done in our `main.ts` file which is responsible for bootstrapping our Angular app. Before the `platformBrowserDynamic().bootstrapModule(AppModule)` call that loads our app, we can initialise Segment by adding back `analytics._writeKey="YOUR_WRITE_KEY"` and `analytics.load("YOUR_WRITE_KEY");` we removed from the tracking snippet earlier but replace `"YOUR_WRITE_KEY"` with our environment variable `segmentWriteKey`.

Trying out this line of code as is though, makes TypeScript unhappy with the following error:
```bash
Cannot find name 'analytics'. ts(2304)
```
Well, this means that analytics isn't defined anywhere, but with the way Segment works, it should be part of the `window` object. So, let's try changing it to `window.analytics.load(environment.analytics.segmentWriteKey)`. This gives us a different error:
```bash
Property 'analytics' does not exist on type 'Window & typeof globalThis'. ts(2339)
```

> A different error message, some progress!

This is because TypeScript still does not know if the `analytics` object exists or the interface it provides within the `window` object. Thankfully, we can strongly type the analytics object by installing its types as a dev dependency using:

```bash
npm install --save-dev @types/segment-analytics
```
With the types installed, there's one last thing we need to do, to make TypeScript happy with using the `analytics` object anywhere within our code. That bit is making sure we include the segment-analytics namespace within our `tsconfig.app.json` [types](https://www.typescriptlang.org/tsconfig#types) property like so:

```json
// file: tsconfig.app.json

{
  "extends": "./tsconfig.json",
  "compilerOptions": {
    "outDir": "./out-tsc/app",
    "types": ["segment-analytics"]
  },
  "files": [
    "src/main.ts",
    "src/polyfills.ts"
  ],
  "include": [
    "src/**/*.d.ts"
  ]
}

```
Remember to restart your dev server with `ng serve` after updating `tsconfig.app.json`!

With this in place, we can now call all functions exposed by the analytics object anywhere in our code and enjoy all of TypeScript's goodness when doing so. Our final `main.ts` file should now look like:

```ts
// file: main.ts

import { enableProdMode } from '@angular/core';
import { platformBrowserDynamic } from '@angular/platform-browser-dynamic';

import { AppModule } from './app/app.module';
import { environment } from './environments/environment';

if (environment.production) {
  enableProdMode();
}

// typing this as any since the types we installed don't include _writeKey
(analytics as any)._writeKey = environment.analytics.segmentWriteKey
analytics.load(environment.analytics.segmentWriteKey)

platformBrowserDynamic().bootstrapModule(AppModule)
  .catch(err => console.error(err));
```

## Tracking page changes

Our final task is to now bring back the `analytics.page()` call we removed from the snippet earlier. Segment analytics includes this call in the tracking snippet assuming its usage in a traditional Multi Page Application (MPA) where we load a new `index.html` file for each page and thus invoke a new `analytics.page()` call.

With Angular though, this is a different story as we're building a Single Page Application (SPA) that only loads a single `index.html` file and handles page changes via JavaScript. So, [we need to take control of the analytics page calls](https://segment.com/blog/tracking-single-page-application/).

This is a perfect use-case for [Angular Router Events](https://angular.io/api/router/Event) as it allows us to hook into the lifecycle of the Angular router and subscribe to page changes. With this, any time the Angular Router loads a new page, we can hook into that event, and make a call to `analytics.page()`. We'll make use of the [NavigationStart](https://angular.io/api/router/NavigationStart) event, so we trigger the call as soon as a page is changed. 

To be able to listen to router events, we first need to inject the Angular Router in our `app.component.ts`:

```ts
import { Router } from '@angular/router';

constructor(private _router: Router) {}
```

Using the injected router we can `subscribe` to all its events:

```ts
this._router.events.subscribe((event) => {
  console.log(event)
})
```
Since we're interested in the `NavigationStart` event only, we can add a check with `instanceof`:

```ts
import { NavigationStart } from '@angular/router';

if (event instanceof NavigationStart) {
  console.log(event)
}
```

If we replace `console.log(event)` in the above snippet with `analytics.page(event.url)`, we can trigger an analytics page call, each time Angular Router changes a page. Putting all of these pieces together and remembering to unsubscribe from our subscription when this component destroys to prevent memory leaks, we get:

```typescript
// file: app.component.ts

import { Subscription } from 'rxjs';
import { NavigationStart, Router } from '@angular/router';
import { Component, OnDestroy, OnInit } from '@angular/core';

@Component({
  selector: 'app-root',
  templateUrl: './app.component.html',
  styleUrls: ['./app.component.css']
})
export class AppComponent implements OnInit, OnDestroy {
  routerEventSubscription: Subscription | undefined;

  constructor(private _router: Router) {}

  public ngOnInit(): void {
    this.routerEventSubscription = this._router.events.subscribe((event) => {
      if (event instanceof NavigationStart) {
        analytics.page(event.url)
      }
    });
  }

  public ngOnDestroy(): void {
    this.routerEventSubscription?.unsubscribe()
  }
}
```

That sets us up for using Segment analytics in our Angular app! 🎉 

For tracking particular events, like clicks, make use of the `analytics.track()` call which would also now be strongly typed thanks to the `@types/segment-analytics` package we set up.

## Bonus – setting up the staging environment

As an added bonus, let's look at setting up a staging (or QA) environment for the app. We'll make use of the `configuration` and `fileReplacement` properties in `angular.json` which allow us to create a new build target and [specify specific file replacements at compile time](https://angular.io/guide/workspace-config#additional-build-and-test-options).

To do this, we'll duplicate the `production` configuration, rename the key to `staging` and within `fileReplacements`, specify `environment.ts` to be replaced with `environment.staging.ts` when we build the app for staging:

```json
"staging": {
  "budgets": [
    {
      "type": "initial",
      "maximumWarning": "500kb",
      "maximumError": "1mb"
    },
    {
      "type": "anyComponentStyle",
      "maximumWarning": "2kb",
      "maximumError": "4kb"
    }
  ],
  "fileReplacements": [
    {
      "replace": "src/environments/environment.ts",
      "with": "src/environments/environment.staging.ts"
    }
  ],
  "outputHashing": "all"
},
```

Remember to create the `environment.staging.ts` file! Our new configuration will now be used when building the app with `ng build --configuration staging`

> [View the code from this article on GitHub](https://github.com/A7xSV/segment-analytics-with-angular)

Thanks for reading!



