Skip to content

Instantly share code, notes, and snippets.

@bojidaryovchev
Last active January 12, 2023 21:05
Show Gist options
  • Star 0 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save bojidaryovchev/7de8d32df3a98568f7e4056c70197e4d to your computer and use it in GitHub Desktop.
Save bojidaryovchev/7de8d32df3a98568f7e4056c70197e4d to your computer and use it in GitHub Desktop.
Angular File Upload while tracking the progress (enctype: multipart/form-data)
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