2

I have an Angular app where I need to get the contents of a file from a REST API and the generate a file in the client.

As I can not write a new file in client, I use this question as a workaround.

So basically what it does is create a Blob with the contents and generate a download link, and then simulates a click in the link so that the document gets downloaded.

This is my code:

download(filename) {
    this.service
      .downloadFile(filename)
      .subscribe(data => {
        console.log(data.document);
        this.downloadFile(data.document, filename);
      }, err => this.info = err);
  }

this gets the data from the server

private downloadFile(content: string, filename: string) {
    const link = document.createElement('a');
    link.setAttribute('download', filename);
    link.href = this.makeTextFileUrl(content, filename);

    document.body.appendChild(link);

    // wait for the link to be added to the document
    window.requestAnimationFrame(function () {
      const event = new MouseEvent('click');
      link.dispatchEvent(event);
      document.body.removeChild(link);
    });
  }

this builds the link and simulates the click to download

private makeTextFileUrl(content: string, filename: string): string {
    let url = null;

    const mime = this.getMimeType(filename);
    console.log(mime);
    const blob = new Blob([content], { type: mime });
    // If we are replacing a previously generated file we need to
    // manually revoke the object URL to avoid memory leaks.
    if (url !== null) {
      window.URL.revokeObjectURL(url);
    }

    url = window.URL.createObjectURL(blob);
    return url;
  }

this generates the url link

private getMimeType(filename: string): string {
    if (filename.indexOf('.docx') !== -1) return 'application/vnd.openxmlformats-officedocument.wordprocessingml.document';
    if (filename.indexOf('.doc') !== -1) return 'application/msword';
    if (filename.indexOf('.pdf') !== -1) return 'application/pdf';
    return 'text/plain';
  }

mime type is different depending on the extension

So, this works for plain text files, but for docx files I get an error when I try to open the file and with pdf the file seems to be blank.

I read that for binary files I need to set the responseType to ArrayBuffer for this to work, so I updated my service get call to this

downloadFile(id: string): Observable<any> {
    return this.authHttp
      .get(`${this.api}/download/${id}`, { responseType: ResponseContentType.ArrayBuffer })
      .map(res => {
        const data = res.json();
        return data;
      });
  }

but now I get an error when I try to download the file, regardless if it's a txt, pdf or docx file SyntaxError: Unexpected token ≻ in JSON at position 0

Does anyone know how to fix this?

Thanks

C.Champagne
  • 5,381
  • 2
  • 23
  • 35
David
  • 3,364
  • 10
  • 41
  • 84

1 Answers1

0

So I finally got this to work.

It turns out that I was using an intermediate API Rest to get the contents of the file and then resend them to my frontend app, but in that step the body was being parsed as a string in thus, what I got in my frontend app was not the original content.

In this thread is the fix to that problem. Resend body from response exactly as it comes

Then I just had to change a few things in my frontend app to get the binary file.

downloadFile(id: string): Observable<any> {
    return this.authHttp
      .get(`${this.api}/${id}`, { responseType: ResponseContentType.ArrayBuffer })
      .map(res => {
        return res.blob();
      });
  }

So now I'm retrieving the data not as a json but as a blob.

Then in my component

download(filename) {
    const id = this.filesIds[filename];
    this.service
      .downloadFile(id)
      .subscribe(blob => {
        this.downloadFile(blob, filename);
      }, err => this.info = err);
  }

  private downloadFile(blob: Blob, filename: string) {
    const link = document.createElement('a');
    link.setAttribute('download', filename);

    link.href = this.makeTextFileUrl(blob, filename);

    document.body.appendChild(link);

    // wait for the link to be added to the document
    window.requestAnimationFrame(function () {
      const event = new MouseEvent('click');
      link.dispatchEvent(event);
      document.body.removeChild(link);
    });
  }

The rest stays the same.

David
  • 3,364
  • 10
  • 41
  • 84