Skip to content

Commit de68e04

Browse files
JeanMecheAndrewKushnir
authored andcommitted
fix(http): Dynamicaly call the global fetch implementation (#57531)
Instead of using the reference that existing when `FetchBackend` is setup. fixes #57527 PR Close #57531
1 parent 2324d9b commit de68e04

File tree

2 files changed

+44
-2
lines changed

2 files changed

+44
-2
lines changed

packages/common/http/src/fetch.ts

+4-2
Original file line numberDiff line numberDiff line change
@@ -52,9 +52,11 @@ function getResponseUrl(response: Response): string | null {
5252
*/
5353
@Injectable()
5454
export class FetchBackend implements HttpBackend {
55-
// We need to bind the native fetch to its context or it will throw an "illegal invocation"
55+
// We use an arrow function to always reference the current global implementation of `fetch`.
56+
// This is helpful for cases when the global `fetch` implementation is modified by external code,
57+
// see https://github.com/angular/angular/issues/57527.
5658
private readonly fetchImpl =
57-
inject(FetchFactory, {optional: true})?.fetch ?? fetch.bind(globalThis);
59+
inject(FetchFactory, {optional: true})?.fetch ?? ((...args) => globalThis.fetch(...args));
5860
private readonly ngZone = inject(NgZone);
5961

6062
handle(request: HttpRequest<any>): Observable<HttpEvent<any>> {

packages/common/http/test/fetch_spec.ts

+40
Original file line numberDiff line numberDiff line change
@@ -12,11 +12,14 @@ import {Observable, of, Subject} from 'rxjs';
1212
import {catchError, retry, scan, skip, take, toArray} from 'rxjs/operators';
1313

1414
import {
15+
HttpClient,
1516
HttpDownloadProgressEvent,
1617
HttpErrorResponse,
1718
HttpHeaderResponse,
1819
HttpParams,
1920
HttpStatusCode,
21+
provideHttpClient,
22+
withFetch,
2023
} from '../public_api';
2124
import {FetchBackend, FetchFactory} from '../src/fetch';
2225

@@ -416,6 +419,43 @@ describe('FetchBackend', async () => {
416419
fetchMock.mockFlush(0, 'CORS 0 status');
417420
});
418421
});
422+
423+
describe('dynamic global fetch', () => {
424+
beforeEach(() => {
425+
TestBed.resetTestingModule();
426+
TestBed.configureTestingModule({
427+
providers: [provideHttpClient(withFetch())],
428+
});
429+
});
430+
431+
it('should use the current implementation of the global fetch', async () => {
432+
const originalFetch = globalThis.fetch;
433+
434+
try {
435+
const fakeFetch = jasmine
436+
.createSpy('', () => Promise.resolve(new Response(JSON.stringify({foo: 'bar'}))))
437+
.and.callThrough();
438+
globalThis.fetch = fakeFetch;
439+
440+
const client = TestBed.inject(HttpClient);
441+
expect(fakeFetch).not.toHaveBeenCalled();
442+
let response = await client.get<unknown>('').toPromise();
443+
expect(fakeFetch).toHaveBeenCalled();
444+
expect(response).toEqual({foo: 'bar'});
445+
446+
// We dynamicaly change the implementation of fetch
447+
const fakeFetch2 = jasmine
448+
.createSpy('', () => Promise.resolve(new Response(JSON.stringify({foo: 'baz'}))))
449+
.and.callThrough();
450+
globalThis.fetch = fakeFetch2;
451+
response = await client.get<unknown>('').toPromise();
452+
expect(response).toEqual({foo: 'baz'});
453+
} finally {
454+
// We need to restore the original fetch implementation, else the tests might become flaky
455+
globalThis.fetch = originalFetch;
456+
}
457+
});
458+
});
419459
});
420460

421461
export class MockFetchFactory extends FetchFactory {

0 commit comments

Comments
 (0)