Skip to content

Commit

Permalink
Run async stuff outside Angular
Browse files Browse the repository at this point in the history
This commit wraps the tracking functionality to run outside of the Angular zone. Previously, it hindered
server-side rendering and hydration, causing instability in the app. The app achieves stability when no
microtasks or macrotasks are running.

On the client side, this change also prevents unnecessary view updates when asynchronous tasks are set
up by `trackPageView`.
  • Loading branch information
arturovt committed Nov 13, 2024
1 parent 643ffcb commit dab5a67
Show file tree
Hide file tree
Showing 2 changed files with 29 additions and 4 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,7 @@ import { Subscription } from "rxjs";
import { AnalyticsPlugin } from "@microsoft/applicationinsights-analytics-js";
import {objDeepFreeze} from "@nevware21/ts-utils";
import { PropertiesPlugin } from "@microsoft/applicationinsights-properties-js";
import { runOutsideAngular } from "./run-outside-angular";

interface IAngularExtensionConfig {
/**
Expand Down Expand Up @@ -113,7 +114,7 @@ export class AngularPlugin extends BaseTelemetryPlugin {
const pageViewTelemetry: IPageViewTelemetry = {
uri: _angularCfg.router.url
};
_self.trackPageView(pageViewTelemetry);
runOutsideAngular(() => _self.trackPageView(pageViewTelemetry));
}

// subscribe to new router events
Expand All @@ -129,7 +130,7 @@ export class AngularPlugin extends BaseTelemetryPlugin {
uri: _angularCfg.router.url,
properties: { duration: 0 } // SPA route change loading durations are undefined, so send 0
};
_self.trackPageView(pvt);
runOutsideAngular(() => _self.trackPageView(pvt));
}
}
});
Expand All @@ -150,7 +151,7 @@ export class AngularPlugin extends BaseTelemetryPlugin {
_propertiesPlugin.context.telemetryTrace.traceID = generateW3CId();
_propertiesPlugin.context.telemetryTrace.name = location && location.pathname || "_unknown_";
}
_analyticsPlugin.trackPageView(pageView);
runOutsideAngular(() => _analyticsPlugin.trackPageView(pageView));
} else {
_throwInternal(_self.diagLog(),
// eslint-disable-next-line max-len
Expand Down Expand Up @@ -186,7 +187,7 @@ export class AngularPlugin extends BaseTelemetryPlugin {
* @param event The event that needs to be processed
*/
processTelemetry(event: ITelemetryItem, itemCtx?: IProcessTelemetryContext) {
this.processNext(event, itemCtx);
runOutsideAngular(() => this.processNext(event, itemCtx));
}


Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,24 @@
// This would be exposed on the `globalThis` whenever `zone.js` is
// included in the `polyfills` configuration property. Starting from Angular 17,
// users can opt-in to use zoneless change detection.
// eslint-disable-next-line @typescript-eslint/naming-convention
declare const Zone: any;

/**
* The function that does the same job as `NgZone.runOutsideAngular`.
* It doesn't require an injection context to be specified.
*
* ⚠️ Note: Most of the Application Insights functionality called from
* inside the Angular execution context must be wrapped in this function.
* Angular's rendering relies on asynchronous tasks being scheduled within
* its execution context.
* Since the plugin schedules tasks that do not interact with Angular's rendering,
* it may prevent Angular from functioning reliably. Consequently, it may disrupt
* processes such as server-side rendering or client-side hydration.
*/
export const runOutsideAngular = <T>(callback: () => T): T =>
// Running the `callback` within the root execution context enables Angular
// processes (such as SSR and hydration) to continue functioning normally without
// timeouts and delays that could affect the user experience.
typeof Zone !== "undefined" ? Zone.root.run(callback) : callback()
;

0 comments on commit dab5a67

Please sign in to comment.