The main purpose of this mechanism is to ease event tracking at component level. You can capture your events via the tracking event directives (exposed in the TrackEventsModule) and the EventTrackService.
You can access all these events via the EventTrackService.
This service is used to store the event objects and to expose them as a stream (observable) to your application. It controls the analytics activation and deactivation as a whole or per feature (ui, performance etc.).
You can directly access the service EventTrackService
inside your component to capture new events.
import {EventTrackService} from '@o3r/analytics';
import {analyticsEvents, MyComponentAnalytics} from './my-component.analytics';
class MyComponent extends Trackable<MyComponentAnalytics>, ... {
...
/**
* @inheritDoc
*/
public readonly analyticsEvents: MyComponentAnalytics = analyticsEvents;
constructor(..., private eventTrackService: EventTrackService) {
...
}
...
somethingHappened() {
this.eventTrackService.addUiEvent(new analyticsEvents.dummyEvent())
}
}
The TrackEventsModule
contains directives to help you track standard event such as the TrackClickDirective
or
TrackFocusDirective
.
You can track more standard ui event with the TrackEventsDirective
and even create your own component events
(see Analytics Events).
Note that all these events will be stored as UI Events in the EventTrackService.
<button
(click)="doSomething()"
trackClick
[trackEventContext]="analyticsEvents.dummyEvent()"></button>
<button
(click)="doSomethingElse()"
trackClick
[trackEventContext]="analyticsEvents.runtimeDummyEvent('You could send runtime data here')"></button>
The directive will listen to the events on the element on which was applied and will expose the event captured using the track service.
Input Name | Description | Possible Values |
---|---|---|
trackEvents | List of events which have to be tracked | ['mouseover', 'mouseenter'] |
trackEventContext | Custom object to be exposed when the event is captured | {context: 'continueBtnClicked'} |
A specific directive for the click event was created, as it is the most used tracked event.
<button type="button" *ngFor="let calendarDate of calendarDates"
[attr.selected]="isSelected"
(click)="onDateSelection()"
[trackEventContext]="{name: 'selectDate', dateSelected: calendarDate}"
[trackEvents]="['click']" <!-- or simply 'trackClick' -->
>
If the object passed in trackEventContext
has to be updated in the model file (ts):
<form [formGroup]="searchForm">
...
<button
[trackEvents]="['click', 'mouseover']"
[trackEventContext]="{value: eventModel | eventContext : {departure: searchForm.controls['departureLocation'], arrival: searchForm.controls['arrivalLocation']}}"
[disabled]="!searchForm.valid"
[attr.id]="id + '-searchButton'">Search</button>
...
</form>
in component.ts file
Example :eventModel = {name: 'searchBtnMouseEvent'};
in eventContext pipe.ts file
Example :transform(value: any, itinerary: any): any {
return {...value, itinerary};
}
At application level a subscription can be done to the observable emitted by the track events service.
You can enhance your analytics data and merge/concatenate/modify the event from the TrackEventsService
with your own
application store.
There are several aspects of a web application that can impact its performance. Network conditions, CPU processing, server-side tasks are a few of them. Checking how long it took to load the page is not enough to measure the application performances. Quickly loading something that is not meaningful nor interactive means nothing to the user. That's why one must improve the load time AND the perceived performance (aka how fast the user perceives the application). Some of those metrics (load time related and perception metrics) are described below.
Mark the first load metrics using the Performance API. This has to be called only once in a single page application, as it is only meaningful for the initial page load. FirstLoadDataPayload interface is the model object for this mark.
This is one of the first metrics for perceived performance. Basically, it measures the time the app takes to answer a user's first question: Is something happening? Is the navigation successful ? Has the server responded? The First Paint (FP) measures the time it takes from the start of the navigation to, for example, display the loading indication.
Also for perceived performance, FMP measures the time the app takes to render enough content for users to engage. A simple strategy for this metric is to mark what's called hero elements (most important elements in the page) and register the time it took to display them
TTI marks the time when the user can effectively interact with the app. This is closely related to the fact that, in some implementations, the app may have rendered meaningful information (measured by FMP) but, in the background, it's still doing some kind of computation that blocks any possible interaction with the page.
The time to interactive is quite tricky as it not only depends on the relevant data readiness, but also on component internal display mechanics. If you know exactly where javascript will trigger a layout change (e.g. by passing a boolean variable to true), it's possible to measure the upper bound for the rendering.
In addition, during a component development, you can't possibly know beforehand if the component will be relevant for a TTI or not, since it depends on the page itself. For example, the display of a cart component may be relevant for TTI in a given page and not relevant at all in others. Hence, you cannot really define your TTI logic at component level.
Given the above facts, we advise to split the TTI metric in two:
For the time being we will consider only the implementation of data ready
As the browser can't understand when a route event happens in an SPA, the NavigationTimingAPI can't be directly used apart from the first page load at most. Subsequent routing changes won't profit of the API connection timings.
In regard of the server fetches (filter out from the resource timing API), the PerformanceMetricPlugin has been put in place to get the metrics associated to server calls. Check ServerCallMetric model to see which information is saved for each call.
The EventTrackService plugs itself to the NavigationEnd router, to handle the performance metrics and exposes the performance object as a stream (observable). The performance metric object structure is defined by PerfEventPayload interface which can be found here. The service provides a way to activate/deactivate performance measurements. By default, it's activated and we expose a public method called togglePerfTracking to activate/deactivate it. For instance if you want to deactivate it, call this in your app:
Example :import {EventTrackService} from '@o3r/analytics';
...
constructor(trackService: EventTrackService) {
trackService.togglePerfTracking(false);
}
You can override the default configuration via a configuration token (EVENT_TRACK_SERVICE_CONFIGURATION). Example of configuration override:
Example :// in app module
...
providers: [
...
{provide: EVENT_TRACK_SERVICE_CONFIGURATION, useValue: {useBrowserApiForFirstFP: true}}
]
More details about the configuration object and defaultEventTrackConfiguration can be found here
This mark is populated by default by the EventTrackService when the NavigationEnd event of the router emits for the first time.
You can mark the time the loading is rendered.
// app component
...
private readonly destroyRef = inject(DestroyRef);
private readonly router = inject(Router);
private readonly trackEventsService = inject(EventTrackService);
ngOnInit() {
this.router.events.pipe(takeUntilDestroyed(this.destroyRef)).subscribe((event) => this.setLoadingIndicator(event));
...
}
setLoadingIndicator(event: Event) {
if (event instanceof NavigationStart) {
this.loading = true;
this.trackEventsService.markFP(); // ----> mark the first paint here
}
}
// in app module
...
providers: [
...
{provide: EVENT_TRACK_SERVICE_CONFIGURATION, useValue: {useBrowserApiForFirstFP: true}}
]
You can mark FMP is in the ngAfterViewInit of each page
Example :// Search component
constructor(... , private trackEventsService: EventTrackService) {...}
ngAfterViewInit() {
this.trackEventsService.markFMP();
}
This will depend on your application. For example, on the availability page, mark data ready when the calendar and offers data are available;
Example :// upsell page component
...
export class UpsellComponent implements OnInit, Configurable<UpsellConfig> {
...
private readonly destroyRef = inject(DestroyRef);
private readonly store = inject(Store<
AirOffersStore
& AirSearchCriteriaStore
& CartStore
& AirCalendarStore
>);
public readonly trackEventsService = inject(EventTrackService);
ngOnInit() {
const airCalendarReady$ = this.store.pipe(
select(selectAirCalendarState),
takeUntilDestroyed(this.destroyRef),
filter((state) => state.isPending === false && state.isFailure === false)
);
const airOffersReady$ = this.store.pipe(
select(selectAirOffersIds),
takeUntilDestroyed(this.destroyRef),
filter((ids) => !!ids.length)
);
void firstValueFrom(
combineLatest([airCalendarReady$, airOffersReady$])
).then(([_airCalendar, _airOffersIds]) => {
this.trackEventsService.markDataReady(); // ----> mark data ready when both calendar and offers data are in the store
});
}
...
}