Angular: How to download a file from HttpClient?

179,737

Solution 1

Try something like this:

type: application/ms-excel

/**
 *  used to get file from server
 */

this.http.get(`${environment.apiUrl}`,{
          responseType: 'arraybuffer',headers:headers} 
         ).subscribe(response => this.downLoadFile(response, "application/ms-excel"));


    /**
     * Method is use to download file.
     * @param data - Array Buffer data
     * @param type - type of the document.
     */
    downLoadFile(data: any, type: string) {
        let blob = new Blob([data], { type: type});
        let url = window.URL.createObjectURL(blob);
        let pwa = window.open(url);
        if (!pwa || pwa.closed || typeof pwa.closed == 'undefined') {
            alert( 'Please disable your Pop-up blocker and try again.');
        }
    }

Solution 2

Blobs are returned with file type from backend. The following function will accept any file type and popup download window:

downloadFile(route: string, filename: string = null): void{

    const baseUrl = 'http://myserver/index.php/api';
    const token = 'my JWT';
    const headers = new HttpHeaders().set('authorization','Bearer '+token);
    this.http.get(baseUrl + route,{headers, responseType: 'blob' as 'json'}).subscribe(
        (response: any) =>{
            let dataType = response.type;
            let binaryData = [];
            binaryData.push(response);
            let downloadLink = document.createElement('a');
            downloadLink.href = window.URL.createObjectURL(new Blob(binaryData, {type: dataType}));
            if (filename)
                downloadLink.setAttribute('download', filename);
            document.body.appendChild(downloadLink);
            downloadLink.click();
        }
    )
}

Solution 3

It took me a while to implement the other responses, as I'm using Angular 8 (tested up to 10). I ended up with the following code (heavily inspired by Hasan).

Note that for the name to be set, the header Access-Control-Expose-Headers MUST include Content-Disposition. To set this in django RF:

http_response = HttpResponse(package, content_type='application/javascript')
http_response['Content-Disposition'] = 'attachment; filename="{}"'.format(filename)
http_response['Access-Control-Expose-Headers'] = "Content-Disposition"

In angular:

  // component.ts
  // getFileName not necessary, you can just set this as a string if you wish
  getFileName(response: HttpResponse<Blob>) {
    let filename: string;
    try {
      const contentDisposition: string = response.headers.get('content-disposition');
      const r = /(?:filename=")(.+)(?:;")/
      filename = r.exec(contentDisposition)[1];
    }
    catch (e) {
      filename = 'myfile.txt'
    }
    return filename
  }

  
  downloadFile() {
    this._fileService.downloadFile(this.file.uuid)
      .subscribe(
        (response: HttpResponse<Blob>) => {
          let filename: string = this.getFileName(response)
          let binaryData = [];
          binaryData.push(response.body);
          let downloadLink = document.createElement('a');
          downloadLink.href = window.URL.createObjectURL(new Blob(binaryData, { type: 'blob' }));
          downloadLink.setAttribute('download', filename);
          document.body.appendChild(downloadLink);
          downloadLink.click();
        }
      )
  }

  // service.ts
  downloadFile(uuid: string) {
    return this._http.get<Blob>(`${environment.apiUrl}/api/v1/file/${uuid}/package/`, { observe: 'response', responseType: 'blob' as 'json' })
  }

Solution 4

I ended up here when searching for ”rxjs download file using post”.

This was my final product. It uses the file name and type given in the server response.

import { ajax, AjaxResponse } from 'rxjs/ajax';
import { map } from 'rxjs/operators';

downloadPost(url: string, data: any) {
    return ajax({
        url: url,
        method: 'POST',
        responseType: 'blob',
        body: data,
        headers: {
            'Content-Type': 'application/json',
            'Accept': 'text/plain, */*',
            'Cache-Control': 'no-cache',
        }
    }).pipe(
        map(handleDownloadSuccess),
    );
}


handleDownloadSuccess(response: AjaxResponse) {
    const downloadLink = document.createElement('a');
    downloadLink.href = window.URL.createObjectURL(response.response);

    const disposition = response.xhr.getResponseHeader('Content-Disposition');
    if (disposition) {
        const filenameRegex = /filename[^;=\n]*=((['"]).*?\2|[^;\n]*)/;
        const matches = filenameRegex.exec(disposition);
        if (matches != null && matches[1]) {
            const filename = matches[1].replace(/['"]/g, '');
            downloadLink.setAttribute('download', filename);
        }
    }

    document.body.appendChild(downloadLink);
    downloadLink.click();
    document.body.removeChild(downloadLink);
}

Solution 5

Using Blob out put from API (Excel FIle)

and tweaked @gabrielrincon answer

downloadExcel(): void {
const payload = {
  order: 'test',
  };

this.service.downloadExcel(payload)
  .subscribe((res: any) => {
    this.blobToFile(res, "application/vnd.openxmlformats-officedocument.spreadsheetml.sheet", "Export.xlsx");
  });}

blob to file common function

blobToFile(data: any, type: string, fileName: string) {
 const a = document.createElement('a');
 document.body.appendChild(a);
 a.style.display = 'none';
 const blob = new Blob([data], { type: type });
 const url = window.URL.createObjectURL(blob);
 a.href = url; a.download = fileName; a.click();
 window.URL.revokeObjectURL(url);}

in the blob to file function we are expecting first parameter as our blob data, type of file and pass file name including extention 1. we are creating an html a tag element 2. then we append the element in html 3. then hide the a tag element 4. then create new blob object with file and type 5. we will convert the blob object to URL 6. then appened that URL to href property of our a tag 7. we are opening our URL in window so it will download

Share:
179,737
Jean Carlos
Author by

Jean Carlos

Developer and maker.

Updated on July 05, 2022

Comments

  • Jean Carlos
    Jean Carlos almost 2 years

    I need download an excel from my backend, its returned a file.

    When I do the request I get the error:

    TypeError: You provided 'undefined' where a stream was expected. You can provide an Observable, Promise, Array, or Iterable.

    My code is:

    this.http.get(`${environment.apiUrl}/...`)
          .subscribe(response => this.downloadFile(response, "application/ms-excel"));
    

    I tried get and map(...) but didn't work.

    Details: angular 5.2

    references:

    import { HttpClient } from '@angular/common/http';
    import 'rxjs/add/observable/throw';
    import 'rxjs/add/operator/finally';
    import 'rxjs/add/operator/map'
    import 'rxjs/add/operator/catch';
    

    Content-Type of response:

    Content-Type: application/ms-excel
    

    What's wrong?

  • Plamen
    Plamen over 5 years
    Great answer!. I would also add "downloadLink.parentNode.removeChild(downloadLink);" after "downloadLink.click();". Just to keep it clear.
  • JoSSte
    JoSSte over 4 years
    after trying several suggestion from this page, a variation of this answer solved my issue
  • bwinchester
    bwinchester about 4 years
    This seems to be the better answer for todays needs and capabilities. It sets the file name using the newer HTML standards. I didn't need the JWT part because we use an intercepter service to add authorization.
  • programmer-man
    programmer-man about 4 years
    I got an error using Angular 9: Type '"arraybuffer"' is not assignable to type '"json"'.ts(2322) http.d.ts(1097, 9): The expected type comes from property 'responseType'.
  • Sean Halls
    Sean Halls about 4 years
    @programmer-man You now need to do something like: resposeType: 'arrayheaders' as 'json' , see Hasan's answer.
  • gabrielrincon
    gabrielrincon almost 4 years
    I modify the download file and I can set the filename and works perfect me: ` downLoadFile(data: any, type: string) { const fileName = 'file1.xlsx'; const a = document.createElement('a'); document.body.appendChild(a); a.style = 'display: none'; const blob = new Blob([data], {type: type}); const url = window.URL.createObjectURL(blob); a.href = url; a.download = fileName; a.click(); window.URL.revokeObjectURL(url); } `
  • Mojtaba
    Mojtaba almost 4 years
    why 'blob' as 'json'? I think you can only say responseType: 'blob'.
  • skilleo
    skilleo over 3 years
    @Mojtaba cuz typing.
  • Satria
    Satria over 3 years
    Yes, it works and I've been using it for a few weeks now, but I came across the fact, that it only works up to a certain filesize. If the returned data is too large (like 1 - 2 MB), the download window doesn't show up. Plus: Even if it worked, on real big files, you wouldn't see the save dialog until all the data has been received. Not a real download...
  • Eluvatar
    Eluvatar over 3 years
    thanks, it worked except for the regex, I'm using asp.net and so the content disposition was a bit different, mine looks like this: '/(?:filename=)(.+)(?:;)/'
  • Deepak Dholiyan
    Deepak Dholiyan almost 3 years
    Is this working in Android and Iphone both?
  • Deepak Dholiyan
    Deepak Dholiyan almost 3 years
    One question:- Why we are not using a tag directly here?
  • cNgamba
    cNgamba almost 3 years
    Can you please explain the blobToFile function?
  • Shabeer M
    Shabeer M almost 3 years
    in the blob to file function we are expecting first parameter as our blob data, type of file and pass file name including extention 1. we are creating an html a tag element 2. then we append the element in html 3. then hide the a tag element 4. then create new blob object with file and type 5. we will convert the blob object to URL 6. then appened that URL to href property of our a tag 7. we are opening our URL in window so it will download
  • cNgamba
    cNgamba over 2 years
    Thanks. Btw how can I display a message on any error?
  • cNgamba
    cNgamba over 2 years
    Say for example if the file is not found I wanted to display a message in my UI
  • Rohit Kumar
    Rohit Kumar over 2 years
    sorry but it won't work for file type .msg & .eml on edge..... what to do then ?
  • kevin628
    kevin628 over 2 years
    Does the a need to actually be added to the body? I didn't appendChild the a and it worked fine.
  • Zarepheth
    Zarepheth over 2 years
    I needed those bits about SafeResourceUrl and bypassSecurityTrustResourceUrl to resolve problems I was having.
  • Matt List
    Matt List almost 2 years
    Thanks! Can confirm this also works with Angular 13