2

I have a href link to download a template from my application. It is working fine in Chrome and IE but however it is not working in mobile devices( Android as well as iPhone)

I have this function which gets called by clicking on the link..

fileUrl: any;

getFileTemplate(): any {
    this.productService.getFile().subscribe((response) => {
      const fileContent = response;
      // An application or a document that must be opened in an application
      const blob = new Blob([fileContent], { type: 'application/octet-stream' });
      if (window.navigator.msSaveBlob) {
        // to download in IE
        window.navigator.msSaveBlob(blob, 'abcd.csv');
      } else {
        this.fileUrl= this.sanitizer.bypassSecurityTrustResourceUrl(window.URL.createObjectURL(blob));
        const a = document.createElement('a');
        a.href = window.URL.createObjectURL(blob);
        a.download = 'abcd.csv';
        a.click();
      }
    });
  }

And in HTML file

`<a href="javascript:void(null)"
   (click)="getFileTemplate();"
   id="link-inline-excel"
   class="u-text--document u-text--document-link"
   download="abcd.csv"><span>Title my file (7MB)</span></a>`

This is not working in mobile devices. Am i missing something here?

V K
  • 87
  • 4
  • 13

1 Answers1

1

You are more or less on the right track. The minimum modification I can offer to get your code to work is adding one more line to getFileTemplate:

getFileTemplate(): any {
  this.productService.getFile().subscribe((response) => {
    const fileContent = response;
    // An application or a document that must be opened in an application
    const blob = new Blob([fileContent], { type: 'application/octet-stream' });
    if (window.navigator.msSaveBlob) {
        // to download in IE
      window.navigator.msSaveBlob(blob, 'abcd.csv');
    } else {
      this.fileUrl= this.sanitizer.bypassSecurityTrustResourceUrl(window.URL.createObjectURL(blob));
      const a = document.createElement('a');
      a.href = window.URL.createObjectURL(blob);
      a.download = 'abcd.csv';

      document.body.appendChild(a); //<-- Need to add the link to the DOM

      a.click();
    }
  });
}

Of course, while this works, it's not a very clean solution. For one, the user will be able to see the newly appended link. For another, the Angular documentation suggests avoiding direct DOM manipulations, using Renderer2 instead.

Here's a StackBlitz example with both of these things in mind.

The example creates a separate Downloader component, that acts like a basic anchor element, but encapsulating the logic to trigger a download. You can then use this component wherever you want to trigger a file download.

The gist of the answer is this snippet:

...

constructor(
  private element: ElementRef,
  private renderer: Renderer2
) {}

...

download(data: Blob, filename: string) {
  if(!data) {
    return;
  }

  //Create the anchor element
  const link: any = this.renderer.createElement('a');

  //Create the URL
  const url: any = URL.createObjectURL(data);

  //Set the attributes for the anchor
  this.renderer.setProperty(link, 'href', url);
  this.renderer.setProperty(link, 'download', filename);

  //Ensure that the anchor will be hidden, both visibly and from screen readers
  this.renderer.setStyle(link, 'display', 'none');

  //Add the anchor element to the DOM
  this.renderer.appendChild(this.element.nativeElement, link);

  //Trigger click on the anchor element to trigger the download
  link.click();

  //Cleanup by removing the element and revoking the URL.
  this.renderer.removeChild(this.element.nativeElement, link);
  URL.revokeObjectURL(url);

  //Note: This is just a basic example, which does do DOM manipulation
  //on every download. You could, instead, append the element in OnInit,
  //adjusting its attributes when a download is triggered, and then
  //remove the element in OnDestroy.
}
Rikonator
  • 1,830
  • 16
  • 27