54

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?

A. Morel
  • 5,749
  • 41
  • 40
Jean Carlos
  • 1,243
  • 3
  • 14
  • 26

6 Answers6

92

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();
        }
    )
}
Hasan
  • 1,334
  • 9
  • 11
  • 4
    Great answer!. I would also add "downloadLink.parentNode.removeChild(downloadLink);" after "downloadLink.click();". Just to keep it clear. – Plamen Nov 10 '18 at 23:32
  • after trying several suggestion from this page, a variation of this answer solved my issue – JoSSte Oct 14 '19 at 16:11
  • 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. – bwinchester Jan 28 '20 at 15:50
  • 2
    why `'blob' as 'json'`? I think you can only say `responseType: 'blob'`. – Mojtaba Jul 17 '20 at 15:09
  • @Mojtaba cuz typing. – twmulloy Jul 30 '20 at 19:01
67

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.');
        }
    }
Akj
  • 6,086
  • 3
  • 23
  • 33
  • 4
    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'. – programmer-man Mar 01 '20 at 13:18
  • @programmer-man You now need to do something like: resposeType: 'arrayheaders' as 'json' , see Hasan's answer. – Sean Halls Apr 20 '20 at 14:52
  • 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); } ` – gabrielrincon May 14 '20 at 16:44
15

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' })
  }

Preston
  • 4,628
  • 3
  • 33
  • 58
  • @TGeorge a pleasure! – Preston Jul 29 '20 at 08:30
  • Works in Angular 9 :) Thanks! – Jeremy Jacob Jul 30 '20 at 20:33
  • 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... – Satria Aug 17 '20 at 09:26
  • 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=)(.+)(?:;)/' – Eluvatar Dec 04 '20 at 00:00
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);
          })    
}
double-beep
  • 3,889
  • 12
  • 24
  • 35
Marc
  • 191
  • 1
  • 5
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);
}
Mikael
  • 1,188
  • 12
  • 13
0

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

Rafi Henig
  • 4,362
  • 1
  • 10
  • 21