|
6 | 6 | * found in the LICENSE file at https://angular.dev/license
|
7 | 7 | */
|
8 | 8 |
|
9 |
| -import {inject, Injectable, InjectionToken, NgZone} from '@angular/core'; |
| 9 | +import {ApplicationRef, inject, Injectable, InjectionToken, NgZone} from '@angular/core'; |
10 | 10 | import {Observable, Observer} from 'rxjs';
|
11 | 11 |
|
12 | 12 | import {HttpBackend} from './backend';
|
@@ -73,6 +73,7 @@ export class FetchBackend implements HttpBackend {
|
73 | 73 | private readonly fetchImpl =
|
74 | 74 | inject(FetchFactory, {optional: true})?.fetch ?? ((...args) => globalThis.fetch(...args));
|
75 | 75 | private readonly ngZone = inject(NgZone);
|
| 76 | + private readonly appRef = inject(ApplicationRef); |
76 | 77 |
|
77 | 78 | handle(request: HttpRequest<any>): Observable<HttpEvent<any>> {
|
78 | 79 | return new Observable((observer) => {
|
@@ -152,6 +153,14 @@ export class FetchBackend implements HttpBackend {
|
152 | 153 | // Here calling the async ReadableStreamDefaultReader.read() is responsible for triggering CD
|
153 | 154 | await this.ngZone.runOutsideAngular(async () => {
|
154 | 155 | while (true) {
|
| 156 | + // Prevent reading chunks if the app is destroyed. Otherwise, we risk doing |
| 157 | + // unnecessary work or triggering side effects after teardown. |
| 158 | + // This may happen if the app was explicitly destroyed before |
| 159 | + // the response returned entirely. |
| 160 | + if (this.appRef.destroyed) { |
| 161 | + break; |
| 162 | + } |
| 163 | + |
155 | 164 | const {done, value} = await reader.read();
|
156 | 165 |
|
157 | 166 | if (done) {
|
|
0 commit comments