With this change we move `XhrFactory` to the root entrypoint of `@angular/commmon`, this is needed so that we can configure `XhrFactory` DI token at a platform level, and not add a dependency  between `@angular/platform-browser` and `@angular/common/http`.
Currently, when using `HttpClientModule` in a child module on the server, `ReferenceError: XMLHttpRequest is not defined` is being thrown because the child module has its own Injector and causes `XhrFactory` provider to be configured to use `BrowserXhr`.
Therefore, we should configure the `XhrFactory` at a platform level similar to other Browser specific providers.
BREAKING CHANGE:
`XhrFactory` has been moved from `@angular/common/http` to `@angular/common`.
**Before**
```ts
import {XhrFactory} from '@angular/common/http';
```
**After**
```ts
import {XhrFactory} from '@angular/common';
```
Closes #41311
PR Close #41313
		
	
			
		
			
				
	
	
		
			152 lines
		
	
	
		
			3.6 KiB
		
	
	
	
		
			TypeScript
		
	
	
	
	
	
			
		
		
	
	
			152 lines
		
	
	
		
			3.6 KiB
		
	
	
	
		
			TypeScript
		
	
	
	
	
	
| /**
 | |
|  * @license
 | |
|  * Copyright Google LLC All Rights Reserved.
 | |
|  *
 | |
|  * Use of this source code is governed by an MIT-style license that can be
 | |
|  * found in the LICENSE file at https://angular.io/license
 | |
|  */
 | |
| 
 | |
| import {XhrFactory} from '@angular/common';
 | |
| import {HttpHeaders} from '@angular/common/http/src/headers';
 | |
| 
 | |
| export class MockXhrFactory implements XhrFactory {
 | |
|   // TODO(issue/24571): remove '!'.
 | |
|   mock!: MockXMLHttpRequest;
 | |
| 
 | |
|   build(): XMLHttpRequest {
 | |
|     return (this.mock = new MockXMLHttpRequest()) as any;
 | |
|   }
 | |
| }
 | |
| 
 | |
| export class MockXMLHttpRequestUpload {
 | |
|   constructor(private mock: MockXMLHttpRequest) {}
 | |
| 
 | |
|   addEventListener(event: 'progress', handler: Function) {
 | |
|     this.mock.addEventListener('uploadProgress', handler);
 | |
|   }
 | |
| 
 | |
|   removeEventListener(event: 'progress', handler: Function) {
 | |
|     this.mock.removeEventListener('uploadProgress');
 | |
|   }
 | |
| }
 | |
| 
 | |
| export class MockXMLHttpRequest {
 | |
|   // Set by method calls.
 | |
|   body: any;
 | |
|   // TODO(issue/24571): remove '!'.
 | |
|   method!: string;
 | |
|   // TODO(issue/24571): remove '!'.
 | |
|   url!: string;
 | |
|   mockHeaders: {[key: string]: string} = {};
 | |
|   mockAborted: boolean = false;
 | |
| 
 | |
|   // Directly settable interface.
 | |
|   withCredentials: boolean = false;
 | |
|   responseType: string = 'text';
 | |
| 
 | |
|   // Mocked response interface.
 | |
|   response: any|undefined = undefined;
 | |
|   responseText: string|undefined = undefined;
 | |
|   responseURL: string|null = null;
 | |
|   status: number = 0;
 | |
|   statusText: string = '';
 | |
|   mockResponseHeaders: string = '';
 | |
| 
 | |
|   listeners: {
 | |
|     error?: (event: ErrorEvent) => void,
 | |
|     timeout?: (event: ErrorEvent) => void,
 | |
|     abort?: () => void,
 | |
|     load?: () => void,
 | |
|     progress?: (event: ProgressEvent) => void,
 | |
|     uploadProgress?: (event: ProgressEvent) => void,
 | |
|   } = {};
 | |
| 
 | |
|   upload = new MockXMLHttpRequestUpload(this);
 | |
| 
 | |
|   open(method: string, url: string): void {
 | |
|     this.method = method;
 | |
|     this.url = url;
 | |
|   }
 | |
| 
 | |
|   send(body: any): void {
 | |
|     this.body = body;
 | |
|   }
 | |
| 
 | |
|   addEventListener(
 | |
|       event: 'error'|'timeout'|'load'|'progress'|'uploadProgress'|'abort',
 | |
|       handler: Function): void {
 | |
|     this.listeners[event] = handler as any;
 | |
|   }
 | |
| 
 | |
|   removeEventListener(event: 'error'|'timeout'|'load'|'progress'|'uploadProgress'|'abort'): void {
 | |
|     delete this.listeners[event];
 | |
|   }
 | |
| 
 | |
|   setRequestHeader(name: string, value: string): void {
 | |
|     this.mockHeaders[name] = value;
 | |
|   }
 | |
| 
 | |
|   getAllResponseHeaders(): string {
 | |
|     return this.mockResponseHeaders;
 | |
|   }
 | |
| 
 | |
|   getResponseHeader(header: string): string|null {
 | |
|     return new HttpHeaders(this.mockResponseHeaders).get(header);
 | |
|   }
 | |
| 
 | |
|   mockFlush(status: number, statusText: string, body?: string) {
 | |
|     if (typeof body === 'string') {
 | |
|       this.responseText = body;
 | |
|     } else {
 | |
|       this.response = body;
 | |
|     }
 | |
|     this.status = status;
 | |
|     this.statusText = statusText;
 | |
|     this.mockLoadEvent();
 | |
|   }
 | |
| 
 | |
|   mockDownloadProgressEvent(loaded: number, total?: number): void {
 | |
|     if (this.listeners.progress) {
 | |
|       this.listeners.progress({lengthComputable: total !== undefined, loaded, total} as any);
 | |
|     }
 | |
|   }
 | |
| 
 | |
|   mockUploadProgressEvent(loaded: number, total?: number) {
 | |
|     if (this.listeners.uploadProgress) {
 | |
|       this.listeners.uploadProgress({
 | |
|         lengthComputable: total !== undefined,
 | |
|         loaded,
 | |
|         total,
 | |
|       } as any);
 | |
|     }
 | |
|   }
 | |
| 
 | |
|   mockLoadEvent(): void {
 | |
|     if (this.listeners.load) {
 | |
|       this.listeners.load();
 | |
|     }
 | |
|   }
 | |
| 
 | |
|   mockErrorEvent(error: any): void {
 | |
|     if (this.listeners.error) {
 | |
|       this.listeners.error(error);
 | |
|     }
 | |
|   }
 | |
| 
 | |
|   mockTimeoutEvent(error: any): void {
 | |
|     if (this.listeners.timeout) {
 | |
|       this.listeners.timeout(error);
 | |
|     }
 | |
|   }
 | |
| 
 | |
|   mockAbortEvent(): void {
 | |
|     if (this.listeners.abort) {
 | |
|       this.listeners.abort();
 | |
|     }
 | |
|   }
 | |
| 
 | |
|   abort() {
 | |
|     this.mockAborted = true;
 | |
|   }
 | |
| }
 |