2016-02-12 16 views
18

Uczę się programu Angular 2 Beta. Zastanawiam się, jak pobrać plik PDF z interfejsu API i wyświetlić go w moim widoku? Próbowałem złożyć zamówienie przy użyciu następujących:Angular 2 pobierz plik PDF z interfejsu API i wyświetl go w widoku

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); 
    }) 
  • Proszę zwrócić uwagę, że używam tylko console.log aby zobaczyć wartość r

Ale zawsze pojawia się następujący komunikat o wyjątku:

"arrayBuffer()" method not implemented on Response superclass

Czy to dlatego, że ta metoda nie jest jeszcze gotowa w Angular 2 Beta? Czy jest jakiś błąd, który popełniłem?

Każda pomoc zostanie doceniona. Dziękuję Ci bardzo.

+0

Wygląda jak obsługa blob() i arrayBuffer() będzie w Ng2 RC5, już wkrótce. – Dave

+0

Oto odpowiedź zgodna z przeglądarką Internet Explorer i przeglądarką Chrome https://stackoverflow.com/a/48467727/3926504 –

Odpowiedz

14

Ta funkcja nie jest jeszcze zaimplementowana w obsłudze protokołu HTTP.

Jako obejście, należy rozszerzyć klasę BrowserXhr z Angular2 jak opisano poniżej, aby ustawić responseType do blob na bazowym obiektu XHR:

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

Następnie trzeba owinąć ładunek odpowiedzi na Blob przedmiot i użyć FileSaver bibliotekę, aby otworzyć okno pobierania:

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 biblioteki muszą być zawarte w pliku HTML:

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

Zobacz ten plunkr: http://plnkr.co/edit/tfpS9k2YOO1bMgXBky5Y?p=preview

Niestety Spowoduje to ustawienie responseType dla wszystkich żądań AJAX. Aby móc ustawić wartość tej właściwości, istnieje więcej aktualizacji do wykonania w klasach XHRConnection i Http.

jako odniesienia zobaczyć te linki:

Edytuj

po przemyśleniu trochę więcej, myślę, że można wykorzystać hierarchicznych wtryskiwacze i skonfigurować ten dostawca tylko na poziomie komponentu, który wykonuje es pobieranie:

@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); 
     }); 
    } 
} 

Ta korekcja będzie zastosowanie tylko dla tego komponentu (nie zapomnij usunąć odpowiednie dostarczenie gdy ładowanie początkowe aplikacji). Komponent do pobrania może być używany w następujący sposób:

@Component({ 
    selector: 'somecomponent', 
    template: ` 
    <download filename="'Granizo.pdf'"></download> 
    ` 
    , directives: [ DownloadComponent ] 
}) 
+0

Witaj, Thierry. Dziękuję za odpowiedź. Twoje obejście działa. Ale niestety wpływa to na wszystkie żądania AJAX w mojej aplikacji. Czy istnieje jakaś sztuczka, aby działało to tylko dla żądania PDF (nie żądania JSON)? – asubanovsky

+0

Nie ma za co! Zaktualizowałem swoją odpowiedź potencjalnym rozwiązaniem tego problemu. To powinno działać w następujący sposób :-) –

+0

Jeszcze raz dziękuję. Jeszcze jedno pytanie: czy istnieje sposób wyświetlania plików PDF zamiast wymuszania pobierania? – asubanovsky

16

Oto jak udało mi się go uruchomić. Moja sytuacja: musiałem pobrać plik PDF z mojego punktu końcowego API i zapisać wynik w postaci pliku PDF w przeglądarce.

Do obsługi zapisywania plików we wszystkich przeglądarkach użyłem modułu FileSaver.js.

Utworzono komponent, który pobiera identyfikator pliku do pobrania jako parametr. Składnik, , nazywa się tak:

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

Komponent sam używa XHR pobrać/zapisać plik z numerem podanym w żadnym parametrem . W ten sposób możemy obejść fakt, że moduł http Angular2 jeszcze nie obsługuje typów wyników binarnych.

A teraz, bez dalszej zwłoki, kod komponent:

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(); 
    } 
} 

Użyłem Font Awesome dla czcionek użytych w szablonie. Chciałem, aby komponent wyświetlał przycisk pobierania i spinner podczas pobierania pliku pdf.

Pamiętaj też, że mogę użyć , aby pobrać moduł fileSaver.js. To dlatego, że używam WebPack, więc mogę wymagać/importować, jak chcę. Twoja składnia może się różnić w zależności od narzędzia do kompilacji.

+2

Dziękuję bardzo! Działa to świetnie. –

+0

Jakieś obejście z kątowym-cli? Ponieważ nie jest łatwo korzystać z funkcji wymagaj/importuj (ponieważ korzystasz z modułu FileSaver.js). Musiałem nawet poprosić o specjalną funkcję wtyczki 3rd party (Angular2 Datepicker), aby działała z kątowym-cli. – asubanovsky

+0

Nie jestem pewien, jak działa kanciasta-cli w zakresie importu stron trzecich. O ile mi wiadomo, korzysta z systemJs i wiem, że z ręcznego grania z nim, powinno być możliwe skonfigurowanie go do importowania zewnętrznych zależności. Ale nie jestem pewien, jak to robi cli-cli ... Znalazłem tę rozmowę (choć dość starą) o tym, jak ręcznie dodawać biblioteki za pomocą ng2-cli, ale nie próbowałem siebie: https://github.com/ kątowe/kątowe-cli/issues/274 – Spock

7

Nie sądzę, że wszystkie te hacki są konieczne. Właśnie wykonałem szybki test ze standardową usługą http w kanale 2.0 i działał zgodnie z oczekiwaniami.

/* generic download mechanism */ 
public download(url: string, data: Object = null): Observable<Response> { 

    //if custom headers are required, add them here 
    let headers = new Headers();   

    //add search parameters, if any 
    let params = new URLSearchParams(); 
    if (data) { 
     for (let key in data) { 
      params.set(key, data[key]); 
     } 
    } 

    //create an instance of requestOptions 
    let requestOptions = new RequestOptions({ 
     headers: headers, 
     search: params 
    }); 

    //any other requestOptions 
    requestOptions.method = RequestMethod.Get; 
    requestOptions.url = url; 
    requestOptions.responseType = ResponseContentType.Blob; 

    //create a generic request object with the above requestOptions 
    let request = new Request(requestOptions); 

    //get the file 
    return this.http.request(request) 
     .catch(err => { 
      /* handle errors */ 
     });  
} 


/* downloads a csv report file generated on the server based on search criteria specified. Save using fileSaver.js. */ 
downloadSomethingSpecifc(searchCriteria: SearchCriteria): void { 

    download(this.url, searchCriteria) 
     .subscribe(
      response => {         
       let file = response.blob(); 
       console.log(file.size + " bytes file downloaded. File type: ", file.type);     
       saveAs(file, 'myCSV_Report.csv'); 
      }, 
      error => { /* handle errors */ } 
     ); 
} 
+0

To powinna być odpowiedź - pozwolił mi korzystać z autoryzacji w czasie rzeczywistym bez konieczności przedłużania Xhr i działał równie dobrze jak najlepsza odpowiedź. – AndrewBenjamin

+1

Co to jest funkcja "saveAs"? Jeśli jest to https://github.com/eligrey/FileSaver.js/, to myślę, że lepiej byłoby dodać import, aby było jasne, o tym – Tarmo

+0

Czy możesz uwzględnić import, którego używałeś? – chris31389

3

Witam, tutaj jest pracy przykład. Nadaje się również do PDF! aplikacja/strumień oktetowy - ogólny. Kontroler:

public FileResult exportExcelTest() 
{ 
    var contentType = "application/octet-stream"; 
    HttpContext.Response.ContentType = contentType; 

    RealisationsReportExcell reportExcell = new RealisationsReportExcell();  
    byte[] filedata = reportExcell.RunSample1(); 

    FileContentResult result = new FileContentResult(filedata, contentType) 
    { 
     FileDownloadName = "report.xlsx" 
    }; 
    return result; 
} 

Angular2:

XHR Usługa:

import { Injectable } from '@angular/core'; 
import { BrowserXhr } from '@angular/http'; 

@Injectable() 
export class CustomBrowserXhr extends BrowserXhr { 
    constructor() { 
     super(); 
    } 

    public build(): any { 
     let xhr = super.build(); 
     xhr.responseType = "blob"; 
     return <any>(xhr); 
    } 
} 

zainstalować plik wygaszacz pakiety NPM "file oszczędzania": "^ 1.3.3", „@ typów/pliku -saver ":" 0.0.0 "i dołącz do pliku vendor.ts importuj" oszczędzanie plików ";

Pobierz składnik btn.

import { Component, OnInit, Input } from "@angular/core"; 
import { Http, ResponseContentType } from '@angular/http'; 
import { CustomBrowserXhr } from '../services/customBrowserXhr.service'; 
import * as FileSaver from 'file-saver'; 

@Component({ 
    selector: 'download-btn', 
    template: '<button type="button" (click)="downloadFile()">Download</button>', 
    providers: [ 
     { provide: CustomBrowserXhr, useClass: CustomBrowserXhr } 
    ] 
}) 

export class DownloadComponent {   
    @Input() api: string; 

    constructor(private http: Http) { 
    } 

    public downloadFile() { 
     return this.http.get(this.api, { responseType: ResponseContentType.Blob }) 
     .subscribe(
      (res: any) => 
      { 
       let blob = res.blob(); 
       let filename = 'report.xlsx'; 
       FileSaver.saveAs(blob, filename); 
      } 
     ); 
    } 
} 

Korzystanie

<download-btn api="api/realisations/realisationsExcel"></download-btn> 
2

Tu jest najprostszym sposobem, aby pobrać plik z API, które udało mi się wymyślić.

import { Injectable } from '@angular/core'; 
import { Http, ResponseContentType } from "@angular/http"; 

import * as FileSaver from 'file-saver'; 

@Injectable() 
export class FileDownloadService { 


    constructor(private http: Http) { } 

    downloadFile(api: string, fileName: string) { 
     this.http.get(api, { responseType: ResponseContentType.Blob }) 
      .subscribe((response: any) => { 
       FileSaver.saveAs(response.blob(), fileName); 
      }); 

    } 

} 

Zadzwoń do metody downloadFile(api,fileName) z klasy komponentów.

Aby uzyskać FileSaver uruchom następujące polecenia w terminalu

npm install file-saver --save 
npm install @types/file-saver --save 
+0

Witaj, dostaję błąd składni: Oto błąd powaga: "Błąd" wiadomość: 'Argument typu' {responseType: ResponseContentType; } "nie można przypisać parametrowi typu" {nagłówki ?: HttpHeaders | {[header: string]: string | strunowy[]; }; obserwować ?: "ciało"; params ?: Ht ... ". Typy właściwości 'responseType' są niezgodne. Typ "ResponseContentType" nie może być przypisany do "" json "". " at: '41, 28 ' źródło:' ts ' kod:' 2345 ' –

0

Oto kod, który działa na downloadign się respone API w IE i chrom/Safari. Tutaj zmienną odpowiedzi jest odpowiedź API.

let blob = new Blob([response], {type: 'application/pdf'}); 
    let fileUrl = window.URL.createObjectURL(blob); 
    if (window.navigator.msSaveOrOpenBlob) { 
     window.navigator.msSaveOrOpenBlob(blob, fileUrl.split(':')[1] + '.pdf'); 
    } else { 
     window.open(fileUrl); 
    }