Skip to content

Navigation Menu

Sign in
Appearance settings

Search code, repositories, users, issues, pull requests...

Provide feedback

We read every piece of feedback, and take your input very seriously.

Saved searches

Use saved searches to filter your results more quickly

Appearance settings

Commit bc57aff

Browse filesBrowse files
committed
fix(core): cleanup testability subscriptions
This commit prevents leaking memory when the application is destroyed and subscriptions are still alive.
1 parent 50993be commit bc57aff
Copy full SHA for bc57aff

File tree

1 file changed

+24
-9
lines changed
Filter options

1 file changed

+24
-9
lines changed

‎packages/core/src/testability/testability.ts

Copy file name to clipboardExpand all lines: packages/core/src/testability/testability.ts
+24-9Lines changed: 24 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -6,7 +6,9 @@
66
* found in the LICENSE file at https://angular.dev/license
77
*/
88

9-
import {Inject, Injectable, InjectionToken} from '../di';
9+
import {inject, Inject, Injectable, InjectionToken} from '../di';
10+
import {isInInjectionContext} from '../di/contextual';
11+
import {DestroyRef} from '../linker/destroy_ref';
1012
import {NgZone} from '../zone/ng_zone';
1113

1214
/**
@@ -84,13 +86,21 @@ export class Testability implements PublicTestability {
8486
private _isZoneStable: boolean = true;
8587
private _callbacks: WaitCallback[] = [];
8688

87-
private taskTrackingZone: {macroTasks: Task[]} | null = null;
89+
private _taskTrackingZone: {macroTasks: Task[]} | null = null;
90+
91+
private _destroyRef?: DestroyRef;
8892

8993
constructor(
9094
private _ngZone: NgZone,
9195
private registry: TestabilityRegistry,
9296
@Inject(TESTABILITY_GETTER) testabilityGetter: GetTestability,
9397
) {
98+
// Attempt to retrieve a `DestroyRef` optionally.
99+
// For backwards compatibility reasons, this cannot be required.
100+
if (isInInjectionContext()) {
101+
this._destroyRef = inject(DestroyRef, {optional: true}) ?? undefined;
102+
}
103+
94104
// If there was no Testability logic registered in the global scope
95105
// before, register the current testability getter as a global one.
96106
if (!_testabilityGetter) {
@@ -99,19 +109,19 @@ export class Testability implements PublicTestability {
99109
}
100110
this._watchAngularEvents();
101111
_ngZone.run(() => {
102-
this.taskTrackingZone =
112+
this._taskTrackingZone =
103113
typeof Zone == 'undefined' ? null : Zone.current.get('TaskTrackingZone');
104114
});
105115
}
106116

107117
private _watchAngularEvents(): void {
108-
this._ngZone.onUnstable.subscribe({
118+
const onUnstableSubscription = this._ngZone.onUnstable.subscribe({
109119
next: () => {
110120
this._isZoneStable = false;
111121
},
112122
});
113123

114-
this._ngZone.runOutsideAngular(() => {
124+
const onStableSubscription = this._ngZone.runOutsideAngular(() =>
115125
this._ngZone.onStable.subscribe({
116126
next: () => {
117127
NgZone.assertNotInAngularZone();
@@ -120,7 +130,12 @@ export class Testability implements PublicTestability {
120130
this._runCallbacksIfReady();
121131
});
122132
},
123-
});
133+
}),
134+
);
135+
136+
this._destroyRef?.onDestroy(() => {
137+
onUnstableSubscription.unsubscribe();
138+
onStableSubscription.unsubscribe();
124139
});
125140
}
126141

@@ -156,12 +171,12 @@ export class Testability implements PublicTestability {
156171
}
157172

158173
private getPendingTasks(): PendingMacrotask[] {
159-
if (!this.taskTrackingZone) {
174+
if (!this._taskTrackingZone) {
160175
return [];
161176
}
162177

163178
// Copy the tasks data so that we don't leak tasks.
164-
return this.taskTrackingZone.macroTasks.map((t: Task) => {
179+
return this._taskTrackingZone.macroTasks.map((t: Task) => {
165180
return {
166181
source: t.source,
167182
// From TaskTrackingZone:
@@ -196,7 +211,7 @@ export class Testability implements PublicTestability {
196211
* and no further updates will be issued.
197212
*/
198213
whenStable(doneCb: Function, timeout?: number, updateCb?: Function): void {
199-
if (updateCb && !this.taskTrackingZone) {
214+
if (updateCb && !this._taskTrackingZone) {
200215
throw new Error(
201216
'Task tracking zone is required when passing an update callback to ' +
202217
'whenStable(). Is "zone.js/plugins/task-tracking" loaded?',

0 commit comments

Comments
0 (0)
Morty Proxy This is a proxified and sanitized view of the page, visit original site.