Angular 2从API下载PDF并在视图中显示

27

我正在学习 Angular 2 Beta。我想知道如何从 API 下载 PDF 文件并在我的视图中显示它?我已经尝试使用以下方式发送请求:

    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);
    })
  • 请注意,我只使用 console.log 来查看 r 的值。

但是我总是收到以下异常消息:

"arrayBuffer()" 方法在 Response 超类上未实现

这是因为 Angular 2 Beta 中该方法还没有准备好吗?还是我犯了什么错误?

任何帮助将不胜感激。非常感谢。


看起来 blob() 和 arrayBuffer() 的支持将在即将推出的 Ng2 RC5 中。 - Dave
这里是与Internet Explorer和Chrome兼容的答案: https://dev59.com/-FsW5IYBdhLWcg3wHUKb#48467727 - Dilip Nannaware
10个回答

18

事实上,HTTP支持中尚未实现此功能。

作为解决方法,您需要按照以下说明扩展Angular2的BrowserXhr类,以将基础xhr对象上的responseType设置为blob

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

然后您需要将响应有效负载包装到Blob对象中,并使用FileSaver库打开下载对话框:

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

必须将 FileSaver 库包含在 HTML 文件中:

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

请参考这个plunkr:http://plnkr.co/edit/tfpS9k2YOO1bMgXBky5Y?p=preview

不幸的是,这将设置所有AJAX请求的responseType。为了能够设置此属性的值,还需要对XHRConnectionHttp类进行更多更新。

作为参考,请参阅以下链接:

编辑

经过思考,我认为您可以利用分层注入器,并仅在执行下载的组件级别上配置此提供程序:

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

这个重载只适用于此组件(在引导应用程序时不要忘记删除相应的提供项)。下载组件可以像这样使用:

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

不客气!我更新了我的答案,提供了一个可能的解决方案。这应该可以像这样工作;-) - Thierry Templier
1
嗨@ThierryTemplier,当我尝试使用自定义Xhr类时,出现“派生类的构造函数必须包含'super'调用”的错误...有什么想法吗?谢谢你提供的好解决方案。 - Spock
1
@ThierryTemplier 你好,谢谢回复。实际上它是空的,但编译器抛出了错误 - 代码仍然被执行了。顺便说一下,我正在使用Typescript 1.8。我在读取blob时遇到了更多麻烦,因为当我运行代码时response._body仍然是私有的。我正在使用你在这里写的相同的代码...非常奇怪。 - Spock
2
大家好,我又来了,很抱歉给你们发了这么多消息,只是想更新一下。我无法解决“_body是私有属性”的问题,所以我决定使用原始的XMLHttpRequest实例化。虽然不是最优雅的解决方案,但它确实有效。...程序员的生活还需要度过更多的艰辛日子 :). 感谢@ThierryTemplier的帮助。 - Spock
我已经在这里发布了一个解决方案,它可以在不使用任何其他 NPM 包的情况下工作。这是链接 - https://dev59.com/-FsW5IYBdhLWcg3wHUKb#48467727 - Dilip Nannaware
显示剩余8条评论

17

以下是我成功实现下载的方法。 我的情况是:我需要从API端点下载PDF,并将结果保存为浏览器中的PDF。

为了支持所有浏览器的文件保存,我使用了FileSaver.js模块。

我创建了一个组件,它将要下载的文件的ID作为参数传入。 该组件名为,调用方式如下:

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

该组件本身使用XHR来获取/保存在no参数中给定的文件号。这样我们就可以规避Angular2 http模块尚不支持二进制结果类型的事实。

现在,不再拖延,呈现组件代码:

    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();
    }
}
我在模板中使用了Font Awesome来处理字体。我希望组件在获取PDF时能够显示下载按钮和旋转图标。

此外,请注意,我可以使用require来获取fileSaver.js模块。这是因为我使用WebPack,所以可以按照自己的需求进行require/import。根据您的构建工具,语法可能会有所不同。

有没有使用angular-cli的变通方法?因为使用require/import不太容易(因为你正在使用FileSaver.js模块)。我甚至不得不请求第三方插件(Angular2日期选择器)的特定功能,以使其与angular-cli一起工作。 - asubanovsky
我不确定angular-cli在第三方导入方面的工作方式。据我所知,它使用systemJs,并且我从手动操作中了解到,应该可以配置它来导入外部依赖项。但是我不确定angular-cli是如何做到这一点的...我发现了这个关于如何使用ng2-cli手动添加库的对话(尽管相当老),但我自己还没有尝试过:https://github.com/angular/angular-cli/issues/274 - Spock
你是如何导入Filesaver.js的?我在使用SystemJS导入第三方JavaScript时遇到了问题。https://dev59.com/Zpjga4cB1Zd3GeqPFBIu - user2180794
嘿,抱歉啊兄弟,我刚看到你的评论。。让我们在组件外部引入文件保存器:let fileSaver = require('filesaver.js'); 然后在组件中使用它:fileSaver.saveAs(blob, fileTitle); - Spock

7

我认为这些黑客技巧都不是必要的。我只是在 Angular 2.0 的标准 HTTP 服务中进行了一个快速测试,它按预期工作。

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

这应该是答案 - 它允许我在不扩展Xhr的情况下使用授权,并且与最佳答案一样有效。 - AndrewBenjamin
2
这里的 'saveAs' 函数是什么?如果它是来自 https://github.com/eligrey/FileSaver.js/,那最好加入导入语句以便明确。 - Tarmo
1
你能把你用过的导入包含进来吗? - chris31389

6
这是我能想到的最简单的从API下载文件的方法。
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: 'blob' })
            .subscribe((file: Blob) => {
               FileSaver.saveAs(file, fileName);
        });    
    }

}

从您的组件类中调用downloadFile(api,fileName)方法。

要运行FileSaver,请在终端中运行以下命令

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

1
你好,我遇到了一个语法错误:以下是错误信息严重程度:'错误' 消息:'类型为“{ responseType: ResponseContentType; }”的参数无法分配给类型“{ headers?:HttpHeaders | { [header:string]:string | string[]; }; observe?:“body”; params?:Ht ...。”的参数。 属性'responseType'的类型不兼容。 类型'ResponseContentType'无法分配给类型'"json"'。' 位置:'41,28' 来源:'ts' 代码:'2345' - Ben Donnelly
@BenDonnelly 应该是这样的。http.get(api, { responseType: 'blob' }) .subscribe((file : Blob) => { FileSaver.saveAs(file, fileName); }); - Olivier Tonglet

4

你好,这是一个可用的示例。它也适合于PDF! application/octet-stream - 一般类型。 控制器:

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

安装文件保存的npm包 "file-saver": "^1.3.3", "@types/file-saver": "0.0.0",并将其包含在vendor.ts中 import 'file-saver';

下载按钮组件。

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

使用

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

2
以下是下载API响应的可在IE、Chrome和Safari中运行的代码。这里的response变量是API响应。
注意:客户端的http调用需要支持blob响应。
    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);
    }

2
为了让 Filesaver 在 Angular 5 中正常工作:安装以下模块:
npm install file-saver --save
npm install @types/file-saver --save

在你的组件中使用import * as FileSaver from "file-saver";,并使用FileSaver.default而不是FileSaver.SaveAs
.subscribe(data => {
            const blob = data.data;
            const filename = "filename.txt";
            FileSaver.default(blob, filename);

0
http
  .post(url, data, {
    responseType: "blob",
    observe: "response"
  })
  .pipe(
    map(response => {
      saveAs(response.body, "fileName.pdf");
    })
  );

1
请添加一些描述,以便原帖作者可以从您的回答中学习。 - Bob Dalgleish
1
虽然这段代码可能解决了问题,但是包括解释它如何以及为什么解决了问题,将有助于提高您的帖子质量,并可能导致更多的赞。请记住,您正在回答未来读者的问题,而不仅仅是现在提问的人。请编辑您的答案以添加解释,并指出适用的限制和假设。 - Busti

0

使用C# Web API加载PDF并将其转换为字节数组的工作解决方案:

C#将PDF作为字节数组加载并转换为Base64编码字符串

public HttpResponseMessage GetPdf(Guid id)
{
    byte[] file = GetFile(id);
    HttpResponseMessage result = Request.CreateResponse(HttpStatusCode.OK);
    result.Content = new StringContent("data:application/pdf;base64," + Convert.ToBase64String(file));
    return result;
}

Angular服务获取PDF

getPdf(): Observable<string> {
    return this.http.get(webApiRequest).pipe(
        map(response => {
            var anonymous = <any>response;
            return anonymous._body;
        })
    );
}

组件视图通过绑定到服务响应来嵌入 PDF

下面的pdfSource变量是从服务返回的值。

<embed [src]="sanitizer.bypassSecurityTrustResourceUrl(pdfSource)" type="application/pdf" width="100%" height="300px" />

查看Angular DomSanitizer文档以获取更多信息。


0

扩展@ThierryTemplier为Angular 8所做的(被接受的答案)。

HTML:

<button mat-raised-button color="accent" (click)="downloadFile()">Download</button>

TypeScript:

downloadFile() {
  this.http.get(
    'http://localhost:4200/assets/other/' + this.fileName, {responseType: 'blob'})
    .pipe(tap( // Log the result or error
      data => console.log(this.fileName, data),
      error => console.log(this.fileName, error)
    )).subscribe(results => {
    saveAs(results, this.fileName);
  });
}

来源:


网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接