Skip to content

Commit 3bb8cc8

Browse files
committed
fix(angular): remove afterSendEvent listener once root injector is destroyed
In this commit, we added cleanup logic to handle the removal of `afterSendEvent`, which is set up within the Angular error handler. This fixes memory leaks that occur when the event is still being handled after the root view is removed.
1 parent a2dcb28 commit 3bb8cc8

File tree

1 file changed

+15
-11
lines changed

1 file changed

+15
-11
lines changed

packages/angular/src/errorhandler.ts

Lines changed: 15 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
import { HttpErrorResponse } from '@angular/common/http';
2-
import type { ErrorHandler as AngularErrorHandler } from '@angular/core';
2+
import type { ErrorHandler as AngularErrorHandler, OnDestroy } from '@angular/core';
33
import { Inject, Injectable } from '@angular/core';
44
import * as Sentry from '@sentry/browser';
55
import type { ReportDialogOptions } from '@sentry/browser';
@@ -80,21 +80,28 @@ function isErrorOrErrorLikeObject(value: unknown): value is Error {
8080
* Implementation of Angular's ErrorHandler provider that can be used as a drop-in replacement for the stock one.
8181
*/
8282
@Injectable({ providedIn: 'root' })
83-
class SentryErrorHandler implements AngularErrorHandler {
83+
class SentryErrorHandler implements AngularErrorHandler, OnDestroy {
8484
protected readonly _options: ErrorHandlerOptions;
8585

86-
/* indicates if we already registered our the afterSendEvent handler */
87-
private _registeredAfterSendEventHandler;
86+
/** The cleanup function is executed when the injector is destroyed. */
87+
private _removeAfterSendEventListener?: VoidFunction;
8888

8989
public constructor(@Inject('errorHandlerOptions') options?: ErrorHandlerOptions) {
90-
this._registeredAfterSendEventHandler = false;
91-
9290
this._options = {
9391
logErrors: true,
9492
...options,
9593
};
9694
}
9795

96+
/**
97+
* Method executed when the injector is destroyed.
98+
*/
99+
public ngOnDestroy(): void {
100+
if (this._removeAfterSendEventListener) {
101+
this._removeAfterSendEventListener();
102+
}
103+
}
104+
98105
/**
99106
* Method called for every value captured through the ErrorHandler
100107
*/
@@ -118,17 +125,14 @@ class SentryErrorHandler implements AngularErrorHandler {
118125
if (this._options.showDialog) {
119126
const client = Sentry.getClient();
120127

121-
if (client && !this._registeredAfterSendEventHandler) {
122-
client.on('afterSendEvent', (event: Event) => {
128+
if (client && !this._removeAfterSendEventListener) {
129+
this._removeAfterSendEventListener = client.on('afterSendEvent', (event: Event) => {
123130
if (!event.type && event.event_id) {
124131
runOutsideAngular(() => {
125132
Sentry.showReportDialog({ ...this._options.dialogOptions, eventId: event.event_id! });
126133
});
127134
}
128135
});
129-
130-
// We only want to register this hook once in the lifetime of the error handler
131-
this._registeredAfterSendEventHandler = true;
132136
} else if (!client) {
133137
runOutsideAngular(() => {
134138
Sentry.showReportDialog({ ...this._options.dialogOptions, eventId });

0 commit comments

Comments
 (0)