Last active
January 12, 2023 21:05
-
-
Save bojidaryovchev/7de8d32df3a98568f7e4056c70197e4d to your computer and use it in GitHub Desktop.
Angular File Upload while tracking the progress (enctype: multipart/form-data)
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
import { HttpClient, HttpEventType, HttpParams, HttpProgressEvent, HttpResponse } from '@angular/common/http'; | |
import { Injectable } from '@angular/core'; | |
import { filter, Observable, of, shareReplay, switchMap } from 'rxjs'; | |
type RequestParams = | |
| HttpParams | |
| { | |
[param: string]: string | number | boolean | readonly (string | number | boolean)[]; | |
}; | |
@Injectable({ | |
providedIn: 'root', | |
}) | |
export class UploadService { | |
constructor(private readonly httpClient: HttpClient) {} | |
/** | |
* Upload while keeping track of the progress | |
* @note $progress and $response are hot observables, we have to make sure to unsubscribe from them | |
*/ | |
upload<T>({ file, params }: { file: File; params?: RequestParams }): { | |
progress$: Observable<number>; | |
response$: Observable<T>; | |
} { | |
const formData = new FormData(); | |
formData.set('file', file); | |
const stream$ = this.httpClient | |
.request('POST', `/upload/endpoint`, { | |
body: formData, | |
params, | |
headers: { | |
enctype: 'multipart/form-data', | |
}, | |
observe: 'events', | |
responseType: 'arraybuffer', | |
reportProgress: true, | |
}) | |
.pipe(shareReplay()); | |
const progress$ = stream$.pipe( | |
filter((httpEvent) => httpEvent.type === HttpEventType.UploadProgress), | |
switchMap((httpEvent) => { | |
const httpProgressEvent = httpEvent as HttpProgressEvent; | |
const progressPercentage = this.calculateProgressPercentage(httpProgressEvent); | |
return of(progressPercentage); | |
}) | |
); | |
const response$ = stream$.pipe( | |
filter((httpEvent) => httpEvent.type === HttpEventType.Response), | |
switchMap((httpEvent) => { | |
const httpResponse = httpEvent as HttpResponse<ArrayBuffer>; | |
if (!httpResponse.body) { | |
return of(); | |
} | |
const response = this.decodeArrayBuffer<T>(httpResponse.body); | |
return of(response); | |
}) | |
); | |
return { | |
progress$, | |
response$, | |
}; | |
} | |
private calculateProgressPercentage(httpProgressEvent: HttpProgressEvent) { | |
return httpProgressEvent.total ? Math.round((100 * httpProgressEvent.loaded) / httpProgressEvent.total) : 0; | |
} | |
private decodeArrayBuffer<T>(arrayBuffer: ArrayBuffer): T { | |
return JSON.parse(String.fromCharCode.apply(null, Array.from(new Uint8Array(arrayBuffer)))); | |
} | |
} |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment