Clycyo
Frameworks5 min read

Angular Analytics: Router Events and Zone-Safe Tracking

The Angular setup for cookieless analytics: index.html installation, Router navigation tracking, and firing events from services cleanly.

Angular apps are single-page applications with opinions, and analytics integrations historically respected those opinions too much: NgModule wrappers, Router event subscriptions, zone.js considerations. The modern approach is simpler — keep the tracker outside Angular entirely and let History API instrumentation do what RouterModule subscriptions used to.

Installation in index.html

<!-- src/index.html -->
<head>
  <script
    defer
    src="https://clycyo.com/tracker.js"
    data-tracking-id="YOUR_TRACKING_ID"
  ></script>
</head>

Angular Router drives navigation through pushState, so route changes are recorded as pageviews automatically — no NavigationEnd subscription, no APP_INITIALIZER. The tracker runs outside Angular's zone, which means zero change-detection cost: analytics never triggers a render cycle.

A typed service for events

// analytics.service.ts
import { Injectable } from '@angular/core';

declare global {
  interface Window {
    webanalytics?: {
      track: (n: string, p?: Record<string, unknown>) => void;
      identify: (id: string, p?: Record<string, unknown>) => void;
      getVisitorId: () => string;
    };
  }
}

@Injectable({ providedIn: 'root' })
export class AnalyticsService {
  track(name: string, props?: Record<string, unknown>) {
    window.webanalytics?.track(name, props);
  }
  identify(email: string) {
    window.webanalytics?.identify(email);
  }
}

Inject it where conversions happen — signup, activation, upgrade clicks — and keep the event vocabulary small and well named.

Identity and revenue

// After login
this.analytics.identify(user.email);
this.http.patch('/api/me', {
  clycyo_visitor_id: window.webanalytics?.getVisitorId(),
}).subscribe();

The persisted visitor ID is what lets your backend's billing webhook post revenue events joined to acquisition context.

Angular-specific notes

  • SSR/hydration (Angular Universal): the tag in index.html renders server-side and executes client-side once — no duplicate pageviews.
  • ErrorHandler overlap: the tracker captures uncaught JS errors with their route; a custom ErrorHandler that swallows errors will hide them from the console but not from window.onerror-level capture.
  • Watch INP: large Angular apps concentrate their slowness in interactions; field INP per real visitor is the number to track, and it arrives automatically.

Total integration: one script tag, one 20-line service. The quickstart has the copy-paste version.