Angular 6从Rest API下载文件

时间:2018-09-03 18:44:36

标签: rest typescript angular6

我有我的REST API,我在其中放置了pdf文件,现在我希望我的有角度的应用程序可以通过Web浏览器点击下载,但我却收到HttpErrorResponse

“ JSON在位置0处出现意外令牌%”

“ SyntaxError:JSON中意外的令牌%在JSON.parse(位置0↵)(

这是我的终点

    @GetMapping("/help/pdf2")
public ResponseEntity<InputStreamResource> getPdf2(){

    Resource resource = new ClassPathResource("/pdf-sample.pdf");
    long r = 0;
    InputStream is=null;

    try {
        is = resource.getInputStream();
        r = resource.contentLength();
    } catch (IOException e) {
        e.printStackTrace();
    }

        return ResponseEntity.ok().contentLength(r)
                .contentType(MediaType.parseMediaType("application/pdf"))
                .body(new InputStreamResource(is));

}

这是我的服务

  getPdf() {

this.authKey = localStorage.getItem('jwt_token');

const httpOptions = {
  headers: new HttpHeaders({
    'Content-Type':  'application/pdf',
    'Authorization' : this.authKey,
    responseType : 'blob',
    Accept : 'application/pdf',
    observe : 'response'
  })
};
return this.http
  .get("http://localhost:9989/api/download/help/pdf2", httpOptions);

}

和调用

this.downloadService.getPdf()
  .subscribe((resultBlob: Blob) => {
  var downloadURL = URL.createObjectURL(resultBlob);
  window.open(downloadURL);});

5 个答案:

答案 0 :(得分:22)

我以这种方式解决了这个问题(请注意,我已经合并了在堆栈溢出时发现的多个解决方案,但是我找不到引用。可以在注释中随意添加它们)。

在我的服务中,我有:

public getPDF(): Observable<Blob> {   
//const options = { responseType: 'blob' }; there is no use of this
    let uri = '/my/uri';
    // this.http refers to HttpClient. Note here that you cannot use the generic get<Blob> as it does not compile: instead you "choose" the appropriate API in this way.
    return this.http.get(uri, { responseType: 'blob' });
}

在组件中,我拥有(这是从多个答案中合并的部分):

public showPDF(): void {
    this.myService.getPDF()
        .subscribe(x => {
            // It is necessary to create a new blob object with mime-type explicitly set
            // otherwise only Chrome works like it should
            var newBlob = new Blob([x], { type: "application/pdf" });

            // IE doesn't allow using a blob object directly as link href
            // instead it is necessary to use msSaveOrOpenBlob
            if (window.navigator && window.navigator.msSaveOrOpenBlob) {
                window.navigator.msSaveOrOpenBlob(newBlob);
                return;
            }

            // For other browsers: 
            // Create a link pointing to the ObjectURL containing the blob.
            const data = window.URL.createObjectURL(newBlob);

            var link = document.createElement('a');
            link.href = data;
            link.download = "Je kar.pdf";
            // this is necessary as link.click() does not work on the latest firefox
            link.dispatchEvent(new MouseEvent('click', { bubbles: true, cancelable: true, view: window }));

            setTimeout(function () {
                // For Firefox it is necessary to delay revoking the ObjectURL
                window.URL.revokeObjectURL(data);
                link.remove();
            }, 100);
        });
}

以上代码可在IE,Edge,Chrome和Firefox中使用。但是,我不太喜欢它,因为我的组件受浏览器特定内容的推崇,这些内容肯定会随着时间而变化。

答案 1 :(得分:10)

我如下解决了该问题:

//header.component.ts
  this.downloadService.getPdf().subscribe((data) => {

  this.blob = new Blob([data], {type: 'application/pdf'});

  var downloadURL = window.URL.createObjectURL(data);
  var link = document.createElement('a');
  link.href = downloadURL;
  link.download = "help.pdf";
  link.click();

});



//download.service.ts
getPdf() {

this.authKey = localStorage.getItem('jwt_token');

const httpOptions = {
  responseType: 'blob' as 'json',
  headers: new HttpHeaders({
    'Authorization': this.authKey,

  })
};

return this.http.get(`${this.BASE_URL}/help/pdf`, httpOptions);

}

答案 2 :(得分:1)

你可以用 angular 指令来做到:

@Directive({
    selector: '[downloadInvoice]',
    exportAs: 'downloadInvoice',
})
export class DownloadInvoiceDirective implements OnDestroy {
    @Input() orderNumber: string;
    private destroy$: Subject<void> = new Subject<void>();
    _loading = false;

    constructor(private ref: ElementRef, private api: Api) {}

    @HostListener('click')
    onClick(): void {
        this._loading = true;
        this.api.downloadInvoice(this.orderNumber)
            .pipe(
                takeUntil(this.destroy$),
                map(response => new Blob([response], { type: 'application/pdf' })),
            )
            .subscribe((pdf: Blob) => {
                this.ref.nativeElement.href = window.URL.createObjectURL(pdf);
                this.ref.nativeElement.click();
            });
    }
    
    // your loading custom class
    @HostBinding('class.btn-loading') get loading() {
        return this._loading;
    }

    ngOnDestroy(): void {
        this.destroy$.next();
        this.destroy$.complete();
    }
}

在模板中:

<a
      downloadInvoice
      [orderNumber]="order.number"
      class="btn-show-invoice"
  >
     Show invoice
  </a>

答案 3 :(得分:0)

我的回答基于@Yennefer,但我想使用服务器中的文件名,因为我的 FE 中没有它。我使用 Content-Disposition 标头传输此信息,因为这是浏览器用于直接下载的内容。

首先,我需要访问请求中的标头(注意 get 方法选项对象):

public getFile(): Observable<HttpResponse<Blob>> {   
    let uri = '/my/uri';
    return this.http.get(uri, { responseType: 'blob', observe: 'response' });
}

接下来,我需要从标题中提取文件名。

public getFileName(res: HttpResponse<any>): string {
    const disposition = res.headers.get('Content-Disposition');
    if (!disposition) {
        // either the disposition was not sent, or is not accessible
        //  (see CORS Access-Control-Expose-Headers)
        return null;
    }
    const utf8FilenameRegex = /filename\*=UTF-8''([\w%\-\.]+)(?:; |$)/;
    const asciiFilenameRegex = /filename=(["'])(.*?[^\\])\1(?:; |$)/;

    let fileName: string = null;
    if (utf8FilenameRegex.test(disposition)) {
      fileName = decodeURIComponent(utf8FilenameRegex.exec(disposition)[1]);
    } else {
      const matches = asciiFilenameRegex.exec(disposition);
      if (matches != null && matches[2]) {
        fileName = matches[2];
      }
    }
    return fileName;
}

此方法检查 ascii 和 utf-8 编码的文件名,首选 utf-8。

一旦我有了文件名,我就可以更新链接对象的下载属性(在@Yennifer 的回答中,就是 link.download = 'FileName.ext'window.navigator.msSaveOrOpenBlob(newBlob, 'FileName.ext'); 行)

关于这段代码的几点说明:

  1. Content-Disposition 不在默认 CORS 白名单中,因此根据您的服务器配置,它可能无法从响应对象访问。如果是这种情况,请在响应服务器中将标头 Access-Control-Expose-Headers 设置为包含 Content-Disposition

  2. 某些浏览器会进一步清理文件名。我的 chrome 版本似乎用下划线替换了 :"。我确定还有其他的,但这超出了范围。

答案 4 :(得分:-1)

这在IE和Chrome中也有效,答案几乎相同,仅适用于其他浏览器。

getPdf(url: string): void {
    this.invoiceService.getPdf(url).subscribe(response => {

      // It is necessary to create a new blob object with mime-type explicitly set
      // otherwise only Chrome works like it should
      const newBlob = new Blob([(response)], { type: 'application/pdf' });

      // IE doesn't allow using a blob object directly as link href
      // instead it is necessary to use msSaveOrOpenBlob
      if (window.navigator && window.navigator.msSaveOrOpenBlob) {
          window.navigator.msSaveOrOpenBlob(newBlob);
          return;
      }

      // For other browsers:
      // Create a link pointing to the ObjectURL containing the blob.
      const downloadURL = URL.createObjectURL(newBlob);
        window.open(downloadURL);
    });
  }