【问题标题】:Angular: How to download a file from HttpClient?Angular:如何从 HttpClient 下载文件?
【发布时间】:2019-01-11 22:20:29
【问题描述】:

我需要从我的后端下载一个 excel,它返回了一个文件。

当我提出请求时,我得到了错误:

TypeError:您在预期流的位置提供了“未定义”。你 可以提供 Observable、Promise、Array 或 Iterable。

我的代码是:

this.http.get(`${environment.apiUrl}/...`)
      .subscribe(response => this.downloadFile(response, "application/ms-excel"));

我尝试了 get 和 map(...) 但没有成功。

详情:angular 5.2

参考:

import { HttpClient } from '@angular/common/http';
import 'rxjs/add/observable/throw';
import 'rxjs/add/operator/finally';
import 'rxjs/add/operator/map'
import 'rxjs/add/operator/catch';

响应的内容类型:

Content-Type: application/ms-excel

怎么了?

【问题讨论】:

  • 错误在哪一行哪一列?
  • 发布downloadFile(....)功能也一样???
  • 我尝试了该链接的回复,但效果不佳。在这里我得到了解决方案!

标签: angular typescript file blob angular-httpclient


【解决方案1】:

从后端返回带有文件类型的 Blob。以下函数将接受任何文件类型并弹出下载窗口:

downloadFile(route: string, filename: string = null): void{

    const baseUrl = 'http://myserver/index.php/api';
    const token = 'my JWT';
    const headers = new HttpHeaders().set('authorization','Bearer '+token);
    this.http.get(baseUrl + route,{headers, responseType: 'blob' as 'json'}).subscribe(
        (response: any) =>{
            let dataType = response.type;
            let binaryData = [];
            binaryData.push(response);
            let downloadLink = document.createElement('a');
            downloadLink.href = window.URL.createObjectURL(new Blob(binaryData, {type: dataType}));
            if (filename)
                downloadLink.setAttribute('download', filename);
            document.body.appendChild(downloadLink);
            downloadLink.click();
        }
    )
}

【讨论】:

  • 很好的答案!。我还要添加“downloadLink.parentNode.removeChild(downloadLink);”在“downloadLink.click();”之后。只是为了保持清楚。
  • 在尝试了这个页面的几个建议之后,这个答案的一个变体解决了我的问题
  • 这似乎是满足当今需求和能力的更好答案。它使用较新的 HTML 标准设置文件名。我不需要 JWT 部分,因为我们使用拦截器服务来添加授权。
  • 为什么是'blob' as 'json'?我想你只能说responseType: 'blob'
  • 一个问题:- 为什么我们这里不直接使用标签?
【解决方案2】:

试试这样的:

类型:应用程序/ms-excel

/**
 *  used to get file from server
 */

this.http.get(`${environment.apiUrl}`,{
          responseType: 'arraybuffer',headers:headers} 
         ).subscribe(response => this.downLoadFile(response, "application/ms-excel"));


    /**
     * Method is use to download file.
     * @param data - Array Buffer data
     * @param type - type of the document.
     */
    downLoadFile(data: any, type: string) {
        let blob = new Blob([data], { type: type});
        let url = window.URL.createObjectURL(blob);
        let pwa = window.open(url);
        if (!pwa || pwa.closed || typeof pwa.closed == 'undefined') {
            alert( 'Please disable your Pop-up blocker and try again.');
        }
    }

【讨论】:

  • 我在使用 Angular 9 时遇到错误:类型 '"arraybuffer"' 不可分配给类型 '"json"'.ts(2322) http.d.ts(1097, 9):预期类型来自属性“responseType”。
  • @programmer-man 您现在需要执行以下操作: resposeType: 'arrayheaders' as 'json' ,请参阅 Hasan 的回答。
  • 我修改了下载文件,我可以设置文件名,完美运行:`downLoadFile(data: any, type: string) { const fileName = 'file1.xlsx'; const a = document.createElement('a'); document.body.appendChild(a); a.style = '显示:无'; const blob = new Blob([data], {type: type});常量 url = window.URL.createObjectURL(blob); a.href = 网址; a.download = 文件名; a.点击(); window.URL.revokeObjectURL(url); } `
【解决方案3】:

我花了一些时间来实现其他响应,因为我使用的是 Angular 8(最多测试 10 个)。我最终得到了以下代码(深受 Hasan 的启发)。

请注意,对于要设置的名称,标题 Access-Control-Expose-Headers 必须包含 Content-Disposition。在 django RF 中设置:

http_response = HttpResponse(package, content_type='application/javascript')
http_response['Content-Disposition'] = 'attachment; filename="{}"'.format(filename)
http_response['Access-Control-Expose-Headers'] = "Content-Disposition"

角度:

  // component.ts
  // getFileName not necessary, you can just set this as a string if you wish
  getFileName(response: HttpResponse<Blob>) {
    let filename: string;
    try {
      const contentDisposition: string = response.headers.get('content-disposition');
      const r = /(?:filename=")(.+)(?:;")/
      filename = r.exec(contentDisposition)[1];
    }
    catch (e) {
      filename = 'myfile.txt'
    }
    return filename
  }

  
  downloadFile() {
    this._fileService.downloadFile(this.file.uuid)
      .subscribe(
        (response: HttpResponse<Blob>) => {
          let filename: string = this.getFileName(response)
          let binaryData = [];
          binaryData.push(response.body);
          let downloadLink = document.createElement('a');
          downloadLink.href = window.URL.createObjectURL(new Blob(binaryData, { type: 'blob' }));
          downloadLink.setAttribute('download', filename);
          document.body.appendChild(downloadLink);
          downloadLink.click();
        }
      )
  }

  // service.ts
  downloadFile(uuid: string) {
    return this._http.get<Blob>(`${environment.apiUrl}/api/v1/file/${uuid}/package/`, { observe: 'response', responseType: 'blob' as 'json' })
  }

【讨论】:

  • 是的,它可以工作,而且我已经使用了几个星期了,但我发现它只能在一定的文件大小下工作。如果返回的数据太大(如 1 - 2 MB),则不会显示下载窗口。另外:即使它在真正的大文件上有效,在收到所有数据之前您也不会看到保存对话框。不是真正的下载...
  • 谢谢,除了正则表达式外它都有效,我使用的是 asp.net,所以内容配置有点不同,我的看起来像这样:'/(?:filename=)(.+ )(?:;)/'
【解决方案4】:

我在搜索“rxjs 使用 post 下载文件”时到了这里。

这是我的最终产品。它使用服务器响应中给出的文件名和类型。

import { ajax, AjaxResponse } from 'rxjs/ajax';
import { map } from 'rxjs/operators';

downloadPost(url: string, data: any) {
    return ajax({
        url: url,
        method: 'POST',
        responseType: 'blob',
        body: data,
        headers: {
            'Content-Type': 'application/json',
            'Accept': 'text/plain, */*',
            'Cache-Control': 'no-cache',
        }
    }).pipe(
        map(handleDownloadSuccess),
    );
}


handleDownloadSuccess(response: AjaxResponse) {
    const downloadLink = document.createElement('a');
    downloadLink.href = window.URL.createObjectURL(response.response);

    const disposition = response.xhr.getResponseHeader('Content-Disposition');
    if (disposition) {
        const filenameRegex = /filename[^;=\n]*=((['"]).*?\2|[^;\n]*)/;
        const matches = filenameRegex.exec(disposition);
        if (matches != null && matches[1]) {
            const filename = matches[1].replace(/['"]/g, '');
            downloadLink.setAttribute('download', filename);
        }
    }

    document.body.appendChild(downloadLink);
    downloadLink.click();
    document.body.removeChild(downloadLink);
}

【讨论】:

    【解决方案5】:

    使用 API(Excel 文件)输出的 Blob

    并调整了@gabrielrincon 答案

    downloadExcel(): void {
    const payload = {
      order: 'test',
      };
    
    this.service.downloadExcel(payload)
      .subscribe((res: any) => {
        this.blobToFile(res, "application/vnd.openxmlformats-officedocument.spreadsheetml.sheet", "Export.xlsx");
      });}
    

    blob 到文件常用函数

    blobToFile(data: any, type: string, fileName: string) {
     const a = document.createElement('a');
     document.body.appendChild(a);
     a.style.display = 'none';
     const blob = new Blob([data], { type: type });
     const url = window.URL.createObjectURL(blob);
     a.href = url; a.download = fileName; a.click();
     window.URL.revokeObjectURL(url);}
    

    在 blob 到文件函数中,我们期望第一个参数作为我们的 blob 数据、文件类型和传递文件名,包括扩展名 1。我们正在创建一个 html a 标签元素 2。然后我们将元素附加到 html 3。然后隐藏 a 标签元素 4. 然后使用文件和类型创建新的 blob 对象 5. 我们将 blob 对象转换为 URL 6. 然后将该 URL 附加到我们的 a 标签的 href 属性 7. 我们在窗口中打开我们的 URL 所以它会下载

    【讨论】:

    • 你能解释一下blobToFile函数吗?
    • 在 blob 到文件函数中,我们期望第一个参数作为我们的 blob 数据、文件类型和传递文件名,包括扩展名 1. 我们正在创建一个 html a 标签元素 2. 然后我们附加该元素在 html 中 3. 然后隐藏 a 标签元素 4. 然后使用文件和类型创建新的 blob 对象 5. 我们将 blob 对象转换为 URL 6. 然后将该 URL 附加到我们的 a 标签的 href 属性 7. 我们正在打开我们的窗口中的 URL,以便下载
    • 谢谢。顺便说一句,我怎样才能显示任何错误消息?
    • 例如,如果找不到文件,我想在我的 UI 中显示一条消息
    【解决方案6】:

    在花了很多时间搜索这个答案的回复后:如何从我用 Node.js 编写的 API RESTful 服务器下载一个简单的图像到一个 Angular 组件应用程序中,我终于在这个 web Angular HttpClient Blob 中找到了一个漂亮的答案。基本上它包括:

    API Node.js 宁静:

       /* After routing the path you want ..*/
      public getImage( req: Request, res: Response) {
    
        // Check if file exist...
        if (!req.params.file) {
          return res.status(httpStatus.badRequest).json({
            ok: false,
            msg: 'File param not found.'
          })
        }
        const absfile = path.join(STORE_ROOT_DIR,IMAGES_DIR, req.params.file);
    
        if (!fs.existsSync(absfile)) {
          return res.status(httpStatus.badRequest).json({
            ok: false,
            msg: 'File name not found on server.'
          })
        }
        res.sendFile(path.resolve(absfile));
      }
    

    经过 Angular 6 测试的组件服务(在我的案例中为 EmployeeService):

      downloadPhoto( name: string) : Observable<Blob> {
        const url = environment.api_url + '/storer/employee/image/' + name;
    
        return this.http.get(url, { responseType: 'blob' })
          .pipe(
            takeWhile( () => this.alive),
            filter ( image => !!image));
      }
    

    模板

     <img [src]="" class="custom-photo" #photo>
    

    组件订阅者和使用:

    @ViewChild('photo') image: ElementRef;
    
    public LoadPhoto( name: string) {
        this._employeeService.downloadPhoto(name)
              .subscribe( image => {
                const url= window.URL.createObjectURL(image);
                this.image.nativeElement.src= url;
              }, error => {
                console.log('error downloading: ', error);
              })    
    }
    

    【讨论】:

      【解决方案7】:

      使用Blob 作为img 的来源:

      模板:

      <img [src]="url">
      

      组件:

       public url : SafeResourceUrl;
      
       constructor(private http: HttpClient, private sanitizer: DomSanitizer) {
         this.getImage('/api/image.jpg').subscribe(x => this.url = x)
       }
      
       public getImage(url: string): Observable<SafeResourceUrl> {
         return this.http
           .get(url, { responseType: 'blob' })
           .pipe(
             map(x => {
               const urlToBlob = window.URL.createObjectURL(x) // get a URL for the blob
               return this.sanitizer.bypassSecurityTrustResourceUrl(urlToBlob); // tell Anuglar to trust this value
             }),
           );
       }
      

      关于trusting save values 的进一步参考

      【讨论】:

      • 我需要那些关于SafeResourceUrlbypassSecurityTrustResourceUrl 的信息来解决我遇到的问题。
      【解决方案8】:

      可能是我迟到了。但是@Hasan 的最后一个回答非常棒。

      我只是做了一些小改动(这不接受如此删除的标题)并获得了成功。

      downloadFile(route: string, filename: string = null): void {
          // const baseUrl = 'http://myserver/index.php/api';   
          this.http.get(route, { responseType: 'blob' }).subscribe(
            (response: any) => {
              let dataType = response.type;
              let binaryData = [];
              binaryData.push(response);
              let downloadLink = document.createElement('a');
              downloadLink.href = window.URL.createObjectURL(new Blob(binaryData, { type: dataType }));
              if (filename) {
                downloadLink.setAttribute('download', filename);
              }
              document.body.appendChild(downloadLink);
              downloadLink.click();
            }
          )
        }
      

      【讨论】:

        猜你喜欢
        • 1970-01-01
        • 2019-08-20
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        • 2018-08-16
        • 1970-01-01
        • 2011-07-14
        相关资源
        最近更新 更多