diff --git a/packages/plugin-angular/src/index.ts b/packages/plugin-angular/src/index.ts index d2dd855834..f7ae64a602 100644 --- a/packages/plugin-angular/src/index.ts +++ b/packages/plugin-angular/src/index.ts @@ -1,6 +1,18 @@ import { ErrorHandler, Injectable } from '@angular/core' import Bugsnag, { Client, Plugin } from '@bugsnag/js' +// This actually should be `ZoneType`, but we can't type it since the `zone.js` +// package may not be installed. +declare const Zone: any; + +// There're 2 types of Angular applications: +// 1) zone-full (by default) +// 2) zone-less +// The developer can avoid importing the `zone.js` package and tells Angular that +// they are responsible for running the change detection by themselves. This is done by +// "nooping" the zone through `CompilerOptions` when bootstrapping the root module (`{ ngZone: 'noop' }`). +const isNgZoneEnabled = typeof Zone !== 'undefined' && !!Zone.root && typeof Zone.root.run === 'function' + @Injectable() export class BugsnagErrorHandler extends ErrorHandler { public bugsnagClient: Client; @@ -35,7 +47,19 @@ export class BugsnagErrorHandler extends ErrorHandler { }) } - this.bugsnagClient._notify(event) + if (isNgZoneEnabled) { + // The `Zone.root.run` will spawn asynchronous tasks within the root zone, a parent for the Angular forked zone. + // That means that `zone.js` won't notify the forked zone about invoking tasks; thus, change detection will not + // be triggered multiple times through `ApplicationRef.tick()`. + // Caretaker note: we could've used `NgZone.runOutsideAngular`, but this will require injecting the `NgZone` + // facade. That will create a breaking change for projects already using the `BugsnagErrorHandler`. + Zone.root.run(() => { + this.bugsnagClient._notify(event) + }) + } else { + this.bugsnagClient._notify(event) + } + ErrorHandler.prototype.handleError.call(this, error) } }