From 82726585ea6e614f7b58cd3a658cb6157d5107d1 Mon Sep 17 00:00:00 2001 From: arturovt Date: Wed, 13 Nov 2024 04:14:02 +0200 Subject: [PATCH] Run async stuff outside Angular 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`. --- ...ationinsights-angularplugin-js.component.ts | 9 +++++---- .../src/lib/run-outside-angular.ts | 18 ++++++++++++++++++ 2 files changed, 23 insertions(+), 4 deletions(-) create mode 100644 projects/applicationinsights-angularplugin-js/src/lib/run-outside-angular.ts diff --git a/projects/applicationinsights-angularplugin-js/src/lib/applicationinsights-angularplugin-js.component.ts b/projects/applicationinsights-angularplugin-js/src/lib/applicationinsights-angularplugin-js.component.ts index ac53ede..e6ef6cd 100644 --- a/projects/applicationinsights-angularplugin-js/src/lib/applicationinsights-angularplugin-js.component.ts +++ b/projects/applicationinsights-angularplugin-js/src/lib/applicationinsights-angularplugin-js.component.ts @@ -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 { /** @@ -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 @@ -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)); } } }); @@ -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 @@ -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)); } diff --git a/projects/applicationinsights-angularplugin-js/src/lib/run-outside-angular.ts b/projects/applicationinsights-angularplugin-js/src/lib/run-outside-angular.ts new file mode 100644 index 0000000..2e61bf6 --- /dev/null +++ b/projects/applicationinsights-angularplugin-js/src/lib/run-outside-angular.ts @@ -0,0 +1,18 @@ +/** + * 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 = (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() +;