Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Angular 2 download PDF from API and Display it in View

Tags:

angular

pdf

I'm learning Angular 2 Beta. I wonder how to download the PDF file from the API and display it in my view? I've tried to make a request using the following:

    var headers = new Headers();     headers.append('Accept', 'application/pdf');     var options = new ResponseOptions({         headers: headers     });     var response = new Response(options);     this.http.get(this.setUrl(endpoint), response).map(res => res.arrayBuffer()).subscribe(r=>{        console.log(r);     }) 
  • Please note that I only use the console.log to see the value of r

But I always get the following exception message:

"arrayBuffer()" method not implemented on Response superclass

Is it because that method isn't ready yet in Angular 2 Beta? Or is there any mistake that I made?

Any help would be appreciated. Thank you very much.

like image 583
asubanovsky Avatar asked Feb 12 '16 17:02

asubanovsky


2 Answers

In fact, this feature isn't implemented yet in the HTTP support.

As a workaround, you need to extend the BrowserXhr class of Angular2 as described below to set the responseType to blob on the underlying xhr object:

import {Injectable} from 'angular2/core'; import {BrowserXhr} from 'angular2/http';  @Injectable() export class CustomBrowserXhr extends BrowserXhr {   constructor() {}   build(): any {     let xhr = super.build();     xhr.responseType = "blob";     return <any>(xhr);   } } 

Then you need to wrap the response payload into a Blob object and use the FileSaver library to open the download dialog:

downloadFile() {   this.http.get(     'https://mapapi.apispark.net/v1/images/Granizo.pdf').subscribe(       (response) => {         var mediaType = 'application/pdf';         var blob = new Blob([response._body], {type: mediaType});         var filename = 'test.pdf';         saveAs(blob, filename);       }); } 

The FileSaver library must be included into your HTML file:

<script src="https://cdnjs.cloudflare.com/ajax/libs/FileSaver.js/2014-11-29/FileSaver.min.js"></script> 

See this plunkr: http://plnkr.co/edit/tfpS9k2YOO1bMgXBky5Y?p=preview

Unfortunately this will set the responseType for all AJAX requests. To be able to set the value of this property, there are more updates to do in the XHRConnection and Http classes.

As references see these links:

  • Download pdf file using jquery ajax
  • Receive zip file, angularJs

Edit

After thinking a bit more, I think that you could leverage hierarchical injectors and configure this provider only at the level of the component that executes the download:

@Component({   selector: 'download',   template: '<div (click)="downloadFile() ">Download</div>'   , providers: [     provide(CustomBrowserXhr,        { useClass: CustomBrowserXhr }   ] }) export class DownloadComponent {   @Input()   filename:string;    constructor(private http:Http) {   }    downloadFile() {     this.http.get(       'https://mapapi.apispark.net/v1/images/'+this.filename).subscribe(         (response) => {           var mediaType = 'application/pdf';           var blob = new Blob([response._body], {type: mediaType});           var filename = 'test.pdf';           saveAs(blob, filename);         });     } } 

This override would only applies for this component (don't forget to remove the corresponding provide when bootstrapping your application). The download component could be used like that:

@Component({   selector: 'somecomponent',   template: `     <download filename="'Granizo.pdf'"></download>   `   , directives: [ DownloadComponent ] }) 
like image 79
Thierry Templier Avatar answered Oct 08 '22 09:10

Thierry Templier


So here is how I managed to get it to work. My situation: I needed to download a PDF from my API endpoint, and save the result as a PDF in the browser.

To support file-saving in all browsers, I used the FileSaver.js module.

I created a component that takes the ID of the file to download as parameter. The component, , is called like this:

<pdf-downloader no="24234232"></pdf-downloader> 

The component itself uses XHR to fetch/save the file with the number given in the no parameter. This way we can circumvent the fact that the Angular2 http module doesn't yet support binary result types.

And now, without further ado, the component code:

    import {Component,Input } from 'angular2/core';     import {BrowserXhr} from 'angular2/http';      // Use Filesaver.js to save binary to file     // https://github.com/eligrey/FileSaver.js/     let fileSaver = require('filesaver.js');       @Component({       selector: 'pdf-downloader',       template: `         <button            class="btn btn-secondary-outline btn-sm "           (click)="download()">             <span class="fa fa-download" *ngIf="!pending"></span>             <span class="fa fa-refresh fa-spin" *ngIf="pending"></span>         </button>         `    })     export class PdfDownloader  {         @Input() no: any;         public pending:boolean = false;         constructor() {}         public download() {          // Xhr creates new context so we need to create reference to this         let self = this;          // Status flag used in the template.         this.pending = true;          // Create the Xhr request object         let xhr = new XMLHttpRequest();         let url =  `/api/pdf/iticket/${this.no}?lang=en`;         xhr.open('GET', url, true);         xhr.responseType = 'blob';          // Xhr callback when we get a result back         // We are not using arrow function because we need the 'this' context         xhr.onreadystatechange = function() {              // We use setTimeout to trigger change detection in Zones             setTimeout( () => { self.pending = false; }, 0);              // If we get an HTTP status OK (200), save the file using fileSaver             if(xhr.readyState === 4 && xhr.status === 200) {                 var blob = new Blob([this.response], {type: 'application/pdf'});                 fileSaver.saveAs(blob, 'Report.pdf');             }         };          // Start the Ajax request         xhr.send();     } } 

I've used Font Awesome for the fonts used in the template. I wanted the component to display a download button and a spinner while the pdf is fetched.

Also, notice I could use require to fetch the fileSaver.js module. This is because I'm using WebPack so I can require/import like I want. Your syntax might be different depending of your build tool.

like image 44
Spock Avatar answered Oct 08 '22 10:10

Spock