|
6 | 6 | * found in the LICENSE file at https://angular.io/license
|
7 | 7 | */
|
8 | 8 |
|
9 |
| -import {inject, Injectable} from '@angular/core'; |
| 9 | +import {inject, Injectable, NgZone} from '@angular/core'; |
10 | 10 | import {Observable, Observer} from 'rxjs';
|
11 | 11 |
|
12 | 12 | import {HttpBackend} from './backend';
|
@@ -48,6 +48,7 @@ export class FetchBackend implements HttpBackend {
|
48 | 48 | // We need to bind the native fetch to its context or it will throw an "illegal invocation"
|
49 | 49 | private readonly fetchImpl =
|
50 | 50 | inject(FetchFactory, {optional: true})?.fetch ?? fetch.bind(globalThis);
|
| 51 | + private readonly ngZone = inject(NgZone); |
51 | 52 |
|
52 | 53 | handle(request: HttpRequest<any>): Observable<HttpEvent<any>> {
|
53 | 54 | return new Observable(observer => {
|
@@ -108,29 +109,36 @@ export class FetchBackend implements HttpBackend {
|
108 | 109 | let decoder: TextDecoder;
|
109 | 110 | let partialText: string|undefined;
|
110 | 111 |
|
111 |
| - while (true) { |
112 |
| - const {done, value} = await reader.read(); |
113 |
| - |
114 |
| - if (done) { |
115 |
| - break; |
116 |
| - } |
117 |
| - |
118 |
| - chunks.push(value); |
119 |
| - receivedLength += value.length; |
120 |
| - |
121 |
| - if (request.reportProgress) { |
122 |
| - partialText = request.responseType === 'text' ? |
123 |
| - (partialText ?? '') + (decoder ??= new TextDecoder).decode(value, {stream: true}) : |
124 |
| - undefined; |
125 |
| - |
126 |
| - observer.next({ |
127 |
| - type: HttpEventType.DownloadProgress, |
128 |
| - total: contentLength ? +contentLength : undefined, |
129 |
| - loaded: receivedLength, |
130 |
| - partialText, |
131 |
| - } as HttpDownloadProgressEvent); |
| 112 | + const reqZone = Zone.current; |
| 113 | + |
| 114 | + // Perform response processing outside of Angular zone to |
| 115 | + // ensure no excessive change detection runs are executed |
| 116 | + // Here calling the async ReadableStreamDefaultReader.read() is responsible for triggering CD |
| 117 | + await this.ngZone.runOutsideAngular(async () => { |
| 118 | + while (true) { |
| 119 | + const {done, value} = await reader.read(); |
| 120 | + |
| 121 | + if (done) { |
| 122 | + break; |
| 123 | + } |
| 124 | + |
| 125 | + chunks.push(value); |
| 126 | + receivedLength += value.length; |
| 127 | + |
| 128 | + if (request.reportProgress) { |
| 129 | + partialText = request.responseType === 'text' ? |
| 130 | + (partialText ?? '') + (decoder ??= new TextDecoder).decode(value, {stream: true}) : |
| 131 | + undefined; |
| 132 | + |
| 133 | + reqZone.run(() => observer.next({ |
| 134 | + type: HttpEventType.DownloadProgress, |
| 135 | + total: contentLength ? +contentLength : undefined, |
| 136 | + loaded: receivedLength, |
| 137 | + partialText, |
| 138 | + } as HttpDownloadProgressEvent)); |
| 139 | + } |
132 | 140 | }
|
133 |
| - } |
| 141 | + }); |
134 | 142 |
|
135 | 143 | // Combine all chunks.
|
136 | 144 | const chunksAll = this.concatChunks(chunks, receivedLength);
|
|
0 commit comments