DEV Community

Cover image for Track User Interactions in Angular, The Declarative Way
Nikhil Raj A
Nikhil Raj A

Posted on

Track User Interactions in Angular, The Declarative Way

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();
}
Enter fullscreen mode Exit fullscreen mode

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>
Enter fullscreen mode Exit fullscreen mode

The component continues to handle the actual product behaviour. The directive handles analytics.

Installation

npm install ng-track-event-directive
Enter fullscreen mode Exit fullscreen mode

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
Enter fullscreen mode Exit fullscreen mode

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)],
};
Enter fullscreen mode Exit fullscreen mode

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);
  },
};
Enter fullscreen mode Exit fullscreen mode

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
  }
}
Enter fullscreen mode Exit fullscreen mode

The template can now declare analytics events.

<button
  (click)="signup()"
  [trackEvent]="trackConfig('signup:clicked', { source: 'hero' })"
>
  Sign up
</button>
Enter fullscreen mode Exit fullscreen mode

When the button is clicked, the adapter receives:

trackingAdapter.track('signup:clicked', {
  source: 'hero',
});
Enter fullscreen mode Exit fullscreen mode

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>
Enter fullscreen mode Exit fullscreen mode

Events ending in :clicked listen for click events.

Hover tracking

<div
  [trackEvent]="trackConfig('pricing-card:hovered', {
    plan: 'pro'
  })"
>
  Pro plan
</div>
Enter fullscreen mode Exit fullscreen mode

Events ending in :hovered listen for mouseenter.

View tracking

<section
  [trackEvent]="trackConfig('features:viewed', {
    page: 'homepage'
  })"
>
  <!-- Feature content -->
</section>
Enter fullscreen mode Exit fullscreen mode

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>
Enter fullscreen mode Exit fullscreen mode

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' }
  )"
/>
Enter fullscreen mode Exit fullscreen mode

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,
  }
);
Enter fullscreen mode Exit fullscreen mode

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)