Angular で PDF ファイルをダウンロードする方法


ユース ケース: バックエンドから受信した blob 応答または base64 でエンコードされた文字列を解析し、その場で PDF ファイルをダウンロードします.

バックエンド サービスが BLOB データを応答として送信する

%PDF-1.5
%âãÏÓ
1 0 obj
/Type/Page/Parent 8 0 R /MediaBox[ 0 0 612 792]/Contents 9 0 R /Resources/XObject/img49352 7 0 R /img49350 6 0 R /img49348 5 0 R /img49347 3 0 R /Font/F2 2 0 R /F4 4 0 R
endobj.................



またはbase64でエンコードされた文字列

data:application/pdf;base64,JVBERi0xLjUKJeLjz9MKMSAwI.....



ここでの最初のステップは、応答を処理して解析することです.応答が base64 文字列の場合、ファイルのダウンロードは BLOB データのみで可能であるため、まずそれを BLOB オブジェクトに変換する必要があります.

private processFileResponse(fileResponseData: any, fileName: string, render: string): void {
  if (render === 'base64') {
    this.base64Response = fileResponseData;
    const binaryString = window.atob(fileResponseData);
    const bytes = new Uint8Array(binaryString.length);
    const binaryToBlob = bytes.map((byte, i) => binaryString.charCodeAt(i));
    const blob = new Blob([binaryToBlob], { type: 'application/pdf' });
    this.downloadFile(blob, fileName, render);
  } else {
    const blob = new Blob([fileResponseData], { type: 'application/pdf' });
    this.downloadFile(blob, fileName, render);
  }
}


BLOB オブジェクトの準備ができたら、ファイルをダウンロードするか、新しいタブでファイルを開き、ユーザーがそのファイルをダウンロードするかどうかを決定できるようにします.

ファイルを直接ダウンロードするシナリオを見てみましょう.
  • createObjectURL() を使用してオブジェクト URL を準備する
  • アンカー タグを作成し、オブジェクト URL を href 属性
  • に割り当てます
  • ダウンロードを開始します

  • private downloadFile(blob: any, fileName: string): void {
      // IE Browser
      if (window.navigator && window.navigator.msSaveOrOpenBlob) {
        window.navigator.msSaveOrOpenBlob(blob, fileName);
        return;
      }
      // Other Browsers
      const url = (window.URL || window.webkitURL).createObjectURL(blob);
      const link = this.renderer.createElement('a');
      this.renderer.setAttribute(link, 'download', fileName);
      this.renderer.setAttribute(link, 'href', url);
      this.renderer.setAttribute(link, 'target', '_blank');
      this.renderer.appendChild(this.elementRef.nativeElement, link);
      link.click();
      this.renderer.removeChild(this.elementRef.nativeElement, link);
    
      setTimeout(() => {
        window.URL.revokeObjectURL(url);
      }, 1000);
    }
    


    Once the process is completed, make sure you have revoked the object URL else that will pile up on the browser's memory



    ファイルをダウンロードするつもりがなく、代わりにファイルを新しいタブで開き、ユーザーがそれをダウンロードするか、単に読んでタブを閉じるかを決定できるようにする場合は、以下の手順に従います.

    private downloadFile(blob: any, fileName: string): void {
      // IE Browser
      if (window.navigator && window.navigator.msSaveOrOpenBlob) {
        window.navigator.msSaveOrOpenBlob(blob, fileName);
        return;
      }
    
      // Other Browsers
      const url = (window.URL || window.webkitURL).createObjectURL(blob);
      window.open(url, '_blank');
    
      // rewoke URL after 15 minutes
      setTimeout(() => {
        window.URL.revokeObjectURL(url);
      }, 15 * 60 * 1000);
    }
    


    ファイルをダウンロードするか、コンテンツを読むだけかをユーザーがより柔軟に判断できるため、個人的にはこのアプローチが気に入っています.このアプローチの唯一の欠点は、ダウンロードしたファイルにカスタム ファイル名を割り当てることができないことです.

    IOS (apple) デバイスでのエッジ ケースの処理



    Safari ブラウザーは、セキュリティ上の理由から、これまでに実装した (BLOB URL を使用した) アプローチをサポートしていません. Safari ブラウザーでのダウンロードを可能にするために、サードパーティのライブラリー file-saver-es を利用します.

    import { saveAs } from 'file-saver-es';
    ...
    private downloadFile(blob: any, fileName: string): void {
      ...
      if (this.useFileSaverAPI()) {
        saveAs(blob, fileName);
      } else {
        // rest of devices and browsers
        window.open(url, '_blank');
      }
    }
    private useFileSaverAPI(): boolean {
      return (/(iPad|iPhone|iPod)/g.test(navigator.platform || navigator.userAgent) || ((navigator.platform || navigator.userAgent) === 'MacIntel' && navigator.maxTouchPoints > 1)) && !window.MSStream;
    }
    


    古い Android Web ビュー (v5 以下) でのエッジ ケースの処理



    Android v5 以下の場合、Web ビューが BLOB オブジェクトを認識しないためにダウンロードが失敗するシナリオがあります. FileReader API を使用してその BLOB オブジェクトを base64 文字列に変換してから、ダウンロードを開始する必要があります.

    if (this.isAndroid()) {
      if (this.base64Response) {
        window.open(this.base64Response, '_blank');
      } else {
        const fileReader = new FileReader();
        fileReader.readAsDataURL(blob);
        fileReader.onloadend = () => {
          window.open(fileReader.result as any, '_blank');
        };
      }
    } else {
      // rest of devices and browsers
      window.open(url, '_blank');
    }
    
    private isAndroid(): boolean {
      return (/(android)/i.test(navigator.platform || navigator.userAgent)
    }