Angular: How to download a file from HttpClient?
Asked Answered
M

9

82

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?

Mckean answered 4/8, 2018 at 4:46 Comment(4)
what line and column is the error at ?Firing
Post downloadFile(....) function as well???Cadence
Possible duplicate of How to download excel/Zip files in Angular 4.Cadence
I tried the responses from that link, but not work well. Here I got the solution!Mckean
D
80

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.');
        }
    }
Dachau answered 4/8, 2018 at 5:14 Comment(3)
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'.Tamaru
@Tamaru You now need to do something like: resposeType: 'arrayheaders' as 'json' , see Hasan's answer.Dita
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); } `Ultrasonic
P
139

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();
        }
    )
}
Profusive answered 6/10, 2018 at 15:21 Comment(11)
Great answer!. I would also add "downloadLink.parentNode.removeChild(downloadLink);" after "downloadLink.click();". Just to keep it clear.Nail
after trying several suggestion from this page, a variation of this answer solved my issueStier
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.Disjoin
why 'blob' as 'json'? I think you can only say responseType: 'blob'.Obara
@Obara cuz typing.Ulane
Is this working in Android and Iphone both?Eadwine
One question:- Why we are not using a tag directly here?Eadwine
sorry but it won't work for file type .msg & .eml on edge..... what to do then ?Diabolo
Does the a need to actually be added to the body? I didn't appendChild the a and it worked fine.Laurin
Man you are a saviour , you saved a lot of my effortNeolamarckism
a.dispatchEvent(new MouseEvent('click')) instead of document.body.appendChild(downloadLink); downloadLink.click();Cheerleader
D
80

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.');
        }
    }
Dachau answered 4/8, 2018 at 5:14 Comment(3)
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'.Tamaru
@Tamaru You now need to do something like: resposeType: 'arrayheaders' as 'json' , see Hasan's answer.Dita
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); } `Ultrasonic
T
27

It took me a while to implement the other responses, as I'm using Angular 8 (tested up to 13). 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' })
  }

Thanksgiving answered 28/2, 2020 at 15:57 Comment(4)
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...Haskins
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=)(.+)(?:;)/'Bryna
Thanks! Can confirm this also works with Angular 13Kurus
Like this answer as it uses a file name sent from the server, however, the regex fails when the file name has space(s) so I tried fixing the regex to (?:filename="?)([^"]+)(?:("?;)). Have a quick test at regex101Bianco
B
9

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

Barcus answered 23/6, 2021 at 10:12 Comment(4)
Can you please explain the blobToFile function?Anticlerical
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 downloadBarcus
Thanks. Btw how can I display a message on any error?Anticlerical
Say for example if the file is not found I wanted to display a message in my UIAnticlerical
G
5

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);
}
Gorham answered 15/7, 2019 at 12:57 Comment(0)
C
4

After spending much time searching for a response to this answer: how to download a simple image from my API restful server written in Node.js into an Angular component app, I finally found a beautiful answer in this web Angular HttpClient Blob. Essentially it consist on:

API Node.js restful:

   /* After routing the path you want ..*/
  public getImage( req: Request, res: Response) {

    // Check if file exist...
    if (!req.params.file) {
      return res.status(httpStatus.badRequest).json({
        ok: false,
        msg: 'File param not found.'
      })
    }
    const absfile = path.join(STORE_ROOT_DIR,IMAGES_DIR, req.params.file);

    if (!fs.existsSync(absfile)) {
      return res.status(httpStatus.badRequest).json({
        ok: false,
        msg: 'File name not found on server.'
      })
    }
    res.sendFile(path.resolve(absfile));
  }

Angular 6 tested component service (EmployeeService on my case):

  downloadPhoto( name: string) : Observable<Blob> {
    const url = environment.api_url + '/storer/employee/image/' + name;

    return this.http.get(url, { responseType: 'blob' })
      .pipe(
        takeWhile( () => this.alive),
        filter ( image => !!image));
  }

Template

 <img [src]="" class="custom-photo" #photo>

Component subscriber and use:

@ViewChild('photo') image: ElementRef;

public LoadPhoto( name: string) {
    this._employeeService.downloadPhoto(name)
          .subscribe( image => {
            const url= window.URL.createObjectURL(image);
            this.image.nativeElement.src= url;
          }, error => {
            console.log('error downloading: ', error);
          })    
}
Corvus answered 18/2, 2019 at 9:49 Comment(0)
E
3

May be I am late. But last answer by @Hasan was superb.

I just make little changes(this was not accepting headers so removed) in this and got success.

downloadFile(route: string, filename: string = null): void {
    // const baseUrl = 'http://myserver/index.php/api';   
    this.http.get(route, { responseType: 'blob' }).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();
      }
    )
  }
Eadwine answered 25/6, 2021 at 16:40 Comment(0)
A
2

Using Blob as a source for an img:

template:

<img [src]="url">

component:

 public url : SafeResourceUrl;

 constructor(private http: HttpClient, private sanitizer: DomSanitizer) {
   this.getImage('/api/image.jpg').subscribe(x => this.url = x)
 }

 public getImage(url: string): Observable<SafeResourceUrl> {
   return this.http
     .get(url, { responseType: 'blob' })
     .pipe(
       map(x => {
         const urlToBlob = window.URL.createObjectURL(x) // get a URL for the blob
         return this.sanitizer.bypassSecurityTrustResourceUrl(urlToBlob); // tell Anuglar to trust this value
       }),
     );
 }

Further reference about trusting save values

Arthrospore answered 3/7, 2020 at 1:56 Comment(1)
I needed those bits about SafeResourceUrl and bypassSecurityTrustResourceUrl to resolve problems I was having.Giavani
C
1

Keep it simple, i am stunned by all the complicated proposed solutions.

If your backend returns the attachment flag and the filename property in its Content-Disposition, you can simply make this javascript call.

window.location = `${environment.apiUrl}/...`;

The browser will download the file without changing the current page.

Cetinje answered 2/6, 2022 at 9:26 Comment(1)
Does this work with API authorization?Infringe

© 2022 - 2024 — McMap. All rights reserved.