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.