13

I'm calling an API to download excel file from the server using the fetch API but it didn't force the browser to download, below is my header response:

HTTP/1.1 200 OK Content-Length: 168667 
Content-Type: application/vnd.openxmlformats-officedocument.spreadsheetml.sheet 
Server: Microsoft-IIS/8.5 
Content-Disposition: attachment; filename=test.xlsx 
Access-Control-Allow-Origin: http://localhost:9000 
Access-Control-Allow-Credentials: true 
Access-Control-Request-Method: POST,GET,PUT,DELETE,OPTIONS 
Access-Control-Allow-Headers: X-Requested-With,Accept,Content-Type,Origin 
Persistent-Auth: true 
X-Powered-By: ASP.NET 
Date: Wed, 24 May 2017 20:18:04 GMT

Below my code that I'm using to call the API :

this.httpClient.fetch(url, {
    method: 'POST',
    body: JSON.stringify(object),
    headers: {
        'Accept': 'application/json',
        'Content-Type': 'application/json'
    }
})
djvg
  • 11,722
  • 5
  • 72
  • 103
Ahmed Saber
  • 489
  • 1
  • 9
  • 21
  • You probably want to use https://stackoverflow.com/posts/44168090/edit to update/edit your question to add a snippet of the code you are using to try to download the file – sideshowbarker May 24 '17 at 22:56
  • How are you "calling an API"? – Tom May 25 '17 at 08:08
  • this.httpClient.fetch(url, { method: 'POST', body: JSON.stringify(object), headers: { 'Accept': 'application/json', 'Content-Type': 'application/json' } }) – Ahmed Saber May 25 '17 at 09:13
  • I found away to download using the blob method but if there is another way to force download ? – Ahmed Saber May 25 '17 at 09:15
  • 4
    As a security _feature_ - it's not possible for JavaScript to start a download to your machine using AJAX - as it doesn't have access to write files to your computer. – Tom May 25 '17 at 11:17
  • related: https://stackoverflow.com/q/37614649 – djvg Nov 03 '22 at 11:37

5 Answers5

26

The browser won't show the usual interaction for the download (display Save As... dialog, etc.), only if you navigate to that resource. It is easier to show the difference with an example:

  1. window.location='http://mycompany.com/'
  2. Load http://mycompany.com/ via XHR/Fetch in the background.

In 1., the browser will load the page and display its content. In 2., the browser will load the raw data and return it to you, but you have to display it yourself.

You have to do something similar with files. You have the raw data, but you have to "display" it yourself. To do this, you need to create an object-URL for your downloaded file and navigate to it:

this.httpClient
    .fetch(url, {method, body, headers})
    .then(response => response.blob())
    .then(blob => URL.createObjectURL(blob))
    .then(url => {
        window.open(url, '_blank');
        URL.revokeObjectURL(url);
    });

This fetches the response, reads it as a blob, creates an objectURL, opens it (in a new tab), then revokes the URL.

More about object-URLs: https://developer.mozilla.org/en-US/docs/Web/API/URL/createObjectURL

balazska
  • 963
  • 6
  • 25
  • this answer will open a new tab to preview the image. But because it's using `URL.revokeObjectURL(url);` the image can't be downloaded – Kevin Chandra Jul 21 '20 at 14:43
8

My solution is based on @VINEE and @balazska solution. I wanted to avoid manipulating document.body or opening a new tab via window.open(url, '_blank');

return fetch(urlEndpoint, options)
  .then((res) => res.blob())
  .then((blob) => URL.createObjectURL(blob))
  .then((href) => {
    Object.assign(document.createElement('a'), {
      href,
      download: 'filename.csv',
    }).click();
  });
Kin
  • 1,435
  • 10
  • 16
4

There are some handy libraries and to solve an issue that I had with CSV/text download I used FileSaver.

Example:

var saveAs = require('file-saver');

fetch('/download/urf/file', {
  headers: {
    'Content-Type': 'text/csv'
  },
  responseType: 'blob'
}).then(response => response.blob())
  .then(blob => saveAs(blob, 'test.csv'));

There is also download.js lib as explained here in this question.

Conrado Fonseca
  • 632
  • 2
  • 6
  • 19
  • Why would you set the request header "Content-Type: application/pdf" if you're making a GET request? – sstur Jun 18 '18 at 04:20
  • @sstur Request headers objects have a guard property. This is not exposed to the Web, but it affects which mutation operations are allowed on the headers object. Found at https://developer.mozilla.org/en-US/docs/Web/API/Fetch_API/Using_Fetch#Headers – Conrado Fonseca Jun 26 '18 at 21:02
  • I still don't think that explains why you would set a Content-Type on a GET request which has no body. What is the meaning/purpose of the Content-Type in this case? – sstur Jun 28 '18 at 00:45
4

You can do it like this using the below function

download(filename) {
 fetch(url , { headers })
 .then(response => response.blob())
 .then(blob => URL.createObjectURL(blob))
 .then(uril => {
 var link = document.createElement("a");
 link.href = uril;
 link.download = filename + ".csv";
 document.body.appendChild(link);
 link.click();
 document.body.removeChild(link);
 });
}

here I want to download a CSV file, So I add .csv to the filename.

VINEE
  • 317
  • 3
  • 5
0

I found another way to download and it will work on IE by using

https://www.npmjs.com/package/downloadjs

Ahmed Saber
  • 489
  • 1
  • 9
  • 21