|
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