While working on Angular applications, I often saw analytics tracking added directly inside component methods.
A click handler would perform the actual user action and also send an analytics event:
onSignup(): void {
this.analytics.track('signup:clicked', {
source: 'hero',
});
this.signup();
}
This works well when there are only a few events. But as the application grows, the same pattern starts appearing across many components.
Components become coupled to the analytics service, tracking logic gets mixed with product behaviour, and changing the analytics provider becomes harder than it should be.
I wanted a simpler approach where:
- Components stay focused on application behaviour
- Tracking remains close to the element being tracked
- The application is not tied to one analytics provider
- Clicks, hovers, and element views follow the same pattern
That is why I created ng-track-event-directive, a small open-source Angular directive for declarative event tracking.
What does it do?
The package provides an Angular directive for tracking:
- Click events
- Hover events
- When an element enters the viewport
- Other standard or custom DOM events
Instead of adding analytics calls inside component methods, tracking can be declared directly in the template:
<button
(click)="signup()"
[trackEvent]="trackConfig('signup:clicked', { source: 'hero' })"
>
Sign up
</button>
The component continues to handle the actual product behaviour. The directive handles analytics.
Installation
npm install ng-track-event-directive
The current 1.5.x version supports Angular 22.
For Angular 21.2, install version 1.3.3 exactly:
npm install ng-track-event-directive@1.3.3
Register a tracking adapter
The directive does not depend on Google Analytics, Mixpanel, Segment, or any other specific provider.
Instead, the application provides a small adapter with a single track method.
// app.config.ts
import { ApplicationConfig } from '@angular/core';
import {
provideTrackingAdapter,
type TrackingAdapter,
} from 'ng-track-event-directive';
const trackingAdapter: TrackingAdapter = {
track(eventName, data) {
console.log('[analytics]', eventName, data);
},
};
export const appConfig: ApplicationConfig = {
providers: [provideTrackingAdapter(trackingAdapter)],
};
The console adapter is useful while setting things up. Later, its implementation can forward events to the analytics provider used by the application.
For example:
const trackingAdapter: TrackingAdapter = {
track(eventName, data) {
analyticsService.track(eventName, data);
},
};
The directive does not need to change when the analytics provider changes.
Use the directive in a component
Import TrackEventDirective and expose the trackConfig helper to the template.
import { Component } from '@angular/core';
import {
TrackEventDirective,
trackConfig,
} from 'ng-track-event-directive';
@Component({
selector: 'app-home',
standalone: true,
imports: [TrackEventDirective],
templateUrl: './home.component.html',
})
export class HomeComponent {
protected readonly trackConfig = trackConfig;
signup(): void {
// Normal signup behaviour
}
}
The template can now declare analytics events.
<button
(click)="signup()"
[trackEvent]="trackConfig('signup:clicked', { source: 'hero' })"
>
Sign up
</button>
When the button is clicked, the adapter receives:
trackingAdapter.track('signup:clicked', {
source: 'hero',
});
Track clicks, hovers, and views
The suffix of the event name decides which interaction should be tracked.
Click tracking
<button
[trackEvent]="trackConfig('checkout:clicked', {
location: 'cart'
})"
>
Continue to checkout
</button>
Events ending in :clicked listen for click events.
Hover tracking
<div
[trackEvent]="trackConfig('pricing-card:hovered', {
plan: 'pro'
})"
>
Pro plan
</div>
Events ending in :hovered listen for mouseenter.
View tracking
<section
[trackEvent]="trackConfig('features:viewed', {
page: 'homepage'
})"
>
<!-- Feature content -->
</section>
Events ending in :viewed use IntersectionObserver to detect when the element enters the viewport.
View events are tracked only once by default. Click and hover events can be tracked multiple times.
Track an event only once
The third argument can be used to control whether an event should fire only once.
<button
[trackEvent]="trackConfig(
'upgrade:clicked',
{ plan: 'pro' },
true
)"
>
Upgrade
</button>
This event will be sent only on the first click.
Use another DOM event
Sometimes an analytics event name should not be tied to the built-in suffixes.
An explicit trigger can be provided:
<input
type="search"
[trackEvent]="trackConfig(
'search:focused',
{ location: 'header' },
{ trigger: 'focus' }
)"
/>
This listens for the standard focus event.
Custom DOM events can be used in the same way:
trackConfig(
'dialog:opened',
{ name: 'upgrade-dialog' },
{
trigger: 'dialog-opened',
once: true,
}
);
Only the configured analytics data is passed to the adapter. The original DOM event is not forwarded automatically.
Why use a directive for analytics?
The main benefit is separation of concerns.
Components remain focused on application behaviour, while templates describe which user interactions should be tracked.
It also gives the application one consistent place to:
- Connect an analytics provider
- Define event naming conventions
- Add common metadata
- Replace or test the analytics integration
- Review which UI elements are being tracked
This becomes especially useful as an application grows and analytics events are added across many features.
Final thoughts
Analytics tracking is a small concern at first, but it can quickly become scattered across a frontend codebase.
A declarative directive keeps the setup visible, consistent, and independent of the analytics provider.
The package is open source, and feedback or contributions are welcome:
Top comments (0)