【问题标题】:Angular 2 download PDF from API and Display it in ViewAngular 2 从 API 下载 PDF 并在视图中显示
【发布时间】:2016-05-23 23:00:36
【问题描述】:

我正在学习 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()”方法未在响应超类上实现

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

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

【问题讨论】:

标签: pdf angular


【解决方案1】:

事实上,这个功能还没有在 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 ]
})

【讨论】:

  • 嗨,蒂埃里。感谢您的回答。您的解决方法有效。但不幸的是,它会影响我的应用程序中的所有 AJAX 请求。有什么技巧可以使它仅适用于 PDF 请求(不是 JSON 请求)吗?
  • 不客气!我用一个潜在的解决方案更新了我的答案。这应该像这样工作;-)
  • 嗨@ThierryTemplier,我收到“派生类的构造函数必须包含一个‘超级’调用。”尝试使用自定义 Xhr 类时出错...有什么想法吗?并感谢您提供出色的解决方案
  • @ThierryTemplier 嗨,感谢您的回复..实际上它是空的,但抛出了错误,但编译器 - 代码无论如何都会被执行.. 我使用 Typescript 1.8 btw .. 我遇到了更多麻烦读取 blob,因为在我运行代码时 response._body 仍然是私有的。我使用的代码与您在此处编写的代码相同。超级奇怪
  • 再次嗨 - 很抱歉给大家发送垃圾邮件 - 只是一个更新。我无法解决“_body 是私有财产”的问题,所以我改为使用原始 XMLHttpRequest 实例化。不是最优雅的解决方案,但它有效。 ... argggh 又是程序员悲惨生活的一天:)。 .感谢@ThierryTemplier 的帮助
【解决方案2】:

这就是我设法让它工作的方法。 我的情况:我需要从我的 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,所以我可以根据需要要求/导入。根据您的构建工具,您的语法可能会有所不同。

【讨论】:

  • angular-cli 的任何解决方法?因为使用 require/import 并不容易(因为您使用的是 FileSaver.js 模块)。我什至不得不请求 3rd 方插件(Angular2 Datepicker)的特定功能才能使其与 angular-cli 一起使用。
  • 我不确定 angular-cli 在 3rd 方导入方面是如何工作的。据我所知,它使用 systemJs,并且我通过手动使用它知道应该可以将其配置为导入外部依赖项。但我不确定 angular-cli 是如何做到的......我发现了这个关于如何使用 ng2-cli 手动添加库的对话(虽然很老),但我自己没有尝试过:github.com/angular/angular-cli/issues/274
  • 你是如何导入 Filesaver.js 的?我有问题stackoverflow.com/questions/37852166/…
  • 嘿,对不起,我直到现在才看到你的评论.. let fileSaver = require('filesaver.js'); (在我的组件之外)...然后在组件中使用它:fileSaver.saveAs(blob, fileTitle);
【解决方案3】:

我不认为所有这些技巧都是必要的。我刚刚使用 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 */ }
        );
}

【讨论】:

  • 这应该是答案——它允许我在 get 上使用授权而无需扩展 Xhr 并且工作得和最佳答案一样好。
  • 这里的“saveAs”功能是什么?如果是github.com/eligrey/FileSaver.js,那么我认为最好包含一个导入以明确它
  • 你能把你用过的导入包括进来吗?
【解决方案4】:

这是我想出的从 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

【讨论】:

  • 您好,我收到语法错误:这是错误严重性:'错误'消息:'Argument of type '{ responseType: ResponseContentType; }' 不能分配给类型为 '{ headers?: HttpHeaders | { [标题:字符串]:字符串 |细绳[]; };观察?:“身体”;参数?:Ht...'。属性“responseType”的类型不兼容。类型 'ResponseContentType' 不可分配给类型 '"json"'。在:'41,28' 来源:'ts' 代码:'2345'
  • @BenDonnelly 应该是 this.http.get(api, { responseType: 'blob' }) .subscribe((file : Blob) => { FileSaver.saveAs(file, fileName); } );
【解决方案5】:

您好,这是一个工作示例。它也适用于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';

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

使用

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

【讨论】:

    【解决方案6】:

    要让 Filesaver 在 Angular 5 中工作:安装

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

    在你的组件中使用import * as FileSaver from "file-saver";

    并使用 FileSaver。默认而不是 FileSaver。SaveAs

    .subscribe(data => {
                const blob = data.data;
                const filename = "filename.txt";
                FileSaver.default(blob, filename);
    

    【讨论】:

      【解决方案7】:

      这是用于在 IE 和 chrome/safari 中下载 API 响应的代码。这里的响应变量是 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);
          }
      

      【讨论】:

        【解决方案8】:

        使用 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 docs 了解更多信息。

        【讨论】:

          【解决方案9】:
          http
            .post(url, data, {
              responseType: "blob",
              observe: "response"
            })
            .pipe(
              map(response => {
                saveAs(response.body, "fileName.pdf");
              })
            );
          

          【讨论】:

          • 请添加一些描述,以便原发帖者向您学习。
          • 虽然此代码可以解决问题,including an explanation 说明如何以及为什么解决问题将真正有助于提高您的帖子质量,并可能导致更多的赞成票。请记住,您正在为将来的读者回答问题,而不仅仅是现在提出问题的人。请编辑您的答案以添加解释并说明适用的限制和假设。
          【解决方案10】:

          扩展 @ThierryTemplier 为 Angular 8 所做的(公认的答案)。

          HTML:

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

          打字稿:

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

          来源:

          【讨论】:

            猜你喜欢
            • 1970-01-01
            • 1970-01-01
            • 1970-01-01
            • 1970-01-01
            • 1970-01-01
            • 1970-01-01
            • 1970-01-01
            • 1970-01-01
            • 1970-01-01
            相关资源
            最近更新 更多