This article describes how a typical browser file download can be triggered using the Angular HttpClient. Typically you can simply introduce a link to the endpoint of the file download into the page and this will work just fine. However, if you use authentication via bearer token etc. and the download endpoint needs authentication, you probably want to use the HttpClient in order to make that download request.
To see how to add a bearer token to all http requests in Angular see one of my previous articles:
Angular 5 HttpInterceptor – Add Bearer Token to HttpClient Requests
In this article I will use a zip file as an example.
Download file as Blob
First of all we need to download the file as a blob object. Therefore we introduce a new service method.
1 2 3 4 5 6 |
public async downloadResource(id: string): Promise<Blob> { const file = await this.http.get<Blob>( this.API_URL + this.ZIP_URL + '/' + id, {responseType: 'blob' as 'json'}).toPromise(); return file; } |
I like working with Promises and async in my code…
Keep a close look at the response type here. It has to be set to ‘blob’. Unfortunately simply setting this to blob leads to a TypeScript error:
1 2 |
Types of property 'responseType' are incompatible. Type '"blob"' is not assignable to type '"json"'. |
So you really have to put in this hack and do a “as ‘json'” in order for this to work.
Triggering a file download
In order to open a save file dialog you have to create an object URL with the blob you get from the service above and then “open” that URL.
1 2 3 4 5 6 7 8 9 10 11 12 |
public async downloadZip(): Promise<void> { const blob = await this.service.downloadResource(this.id); const url = window.URL.createObjectURL(blob); const link = this.downloadZipLink.nativeElement; link.href = url; link.download = 'archive.zip'; link.click(); window.URL.revokeObjectURL(url); } |
I couple of things to note here…
First of all an object created by createObjectURL should also be revoked afterwards with revokeObjectURL.
window.open(url)
I saw a lot of example code that worked with window.open(url) instead of the code I use to open the file saving dialog.
Window.open triggered my popup blocker and some browsers specifically asked if they should open this URL. So this solution did not work for me.
Using a link with a click event
1 |
<a class="download-zip-link" #downloadZipLink></a> |
I simply inserted a new link into my page and set it to display none.
Afterwards you can reference that element by introducing
1 |
@ViewChild('downloadZipLink') private downloadZipLink: ElementRef; |
in your component.
This solutions has the advantage that you can
a) revoke the object easily afterwards and
b) you can specify a name for the downloaded file.
Then you just call the click event of that link and the download will be started. Obviously it is also easy to put in some spinner code etc…
One remark here…. since the file first gets downloaded as a Blob, then an objectURL gets created etc., this whole process might not be feasible for large download files.
If you have questions or comments, please leave them below.
Shouldn’t this line
const blob = await this.service.downloadZip(this.id);
be
const blob = await this.service.downloadResource(this.id);
You are right… probably a mistake I introduced for adjusting the code for the blog post
Aren’t you describing an upload here? I would think a download is pulling it into the browser?
Yes this is a download
Thank you.
For some reason it feels like there should be a simpler way to download an excel file……
It looks like this doesn’t work with IE, have you noticed that?
Add check for IE
if (navigator.appVersion.toString().indexOf(‘.NET’) > 0)
window.navigator.msSaveBlob(blob, filename);
else
{….
https://stackoverflow.com/a/25424299/7825538
Thank you for this. You saved the day. Although my http get is without and only with ‘blob’. There is some angular 5 issue with this.
Whole service method:
getShipmentAttachment(shipmentId: number, attachmentId: number): Observable {
let url: string = this.getFullUrl(
/api/v1/shipments/${shipmentId}/attachments/${attachmentId}
);let headers: HttpHeaders = new HttpHeaders().set(‘Authorization’, this.token);
return this._httpClient.get(url, {
headers: headers,
responseType: ‘blob’ // very important that this is set
})
.pipe(
catchError(this.handleError)
);
}
This won’t work at all in Angular 6 typescript.
Also why are you using promises when Angular 2 + has Observables ?
My service function:
getEvidenceFile(id: number, getFileContent: boolean) {
return this.http.get(environment.baseUrl + ‘upload’ + ‘/’ + id + ‘,’ + getFileContent, {responseType: ‘blob’ as ‘json’})
.map(res => res);
}
My component function called from the selected item of a dropdown…
dropDownValue(event: any) {
// const blob = await this.callService.getEvidenceFile(event.target.value, true);
// const url = window.URL.createObjectURL(blob);
this.callService.getEvidenceFile(event.target.value, true).subscribe(data => {
var binaryData = [];
binaryData.push(data);
var downloadLink = document.createElement(‘a’);
downloadLink.href = window.URL.createObjectURL(new Blob(binaryData));
document.body.appendChild(downloadLink);
downloadLink.click();
});
}
[ts] Property ‘URL’ does not exist on type ‘ (this: Observable , windowBoundaries: Observable) => Observable<Observable>’.
This was very helpful.
> I like working with Promises and async in my code…
+1 – friends don’t let friends do rx
Thaaaaaaaaaaank’s man i missed this: responseType: ‘blob’ as ‘json’
Now works perfectly thanks a million
It works with safari for the iPhone also?
Sorry, did not specifically check. But I assume it does.
createObjectURL went into deprecation 7 years ago and has been effectively deprecated 2 years ago this guide has been unusable for two years. please update it.
Hi tatsu,
Do you have any source for this? I looked at the Mozilla documentation for
URL.createObjectURL
and there is no sign of a deprecation: https://developer.mozilla.org/en-US/docs/Web/API/URL/createObjectURL with full support in all major browsers.There seems to be a change however, when using a MediaStream with an HTMLMediaElement (like Video) which used to utilize
URL.createObjectURL
, but doesn’t any more : https://developer.mozilla.org/en-US/docs/Web/API/HTMLMediaElement/srcObjectMaybe this is what you are referring to?
Any idea how to also rename the files within the zip file? Thanks!
This is great, even years later. Had exactly this scenario, but most examples have you use href to api call which doesn’t work in real authentication scenarios. Thanks!