如何在Angular中使用浏览器本地下载工具以编程方式下载文件?

5

当我请求获取下载zip文件的webservice时,它会在不知不觉中下载文件内容,突然间,该文件就会以已完成下载(100%)的状态出现在下载任务栏中。

使用以下Angular方法:


const endpoint = "http://localhost:8080/download/zip"
this.http.get<Blop>(endpoint, {headers: httpHeaders, responseType: 'blob', reportProgress: true })

这里是我的订阅方法:

this.http.get<Blop>(endpoint, {headers: httpHeaders, responseType: 'blob', reportProgress: true }).subscribe({
  next: data => {
    console.log('blocking or not');
    const blob = new Blob([data as any], { type: 'application/zip' });
    window.location.href = URL.createObjectURL(blob);
  }
})

我注意到我的console.log(...)直到下载结束后才被调用,因此我认为浏览器界面无法检测到下载进度,直到达到window.location.href

如何强制在传输结束之前在下载任务栏中显示下载并在浏览器中监视下载进度?我找不到与异步块或类似内容相关的任何信息。

附注:我的后端正在提供数据流,所以后端不是问题。当直接通过浏览器调用我的API时,我们可以在下载任务栏中看到下载进度。但如果您们有兴趣的话,以下是片段(Spring-Boot):

    @GetMapping("/download/zip")
    fun download(response: HttpServletResponse): StreamingResponseBody {
        val file = downloads.download("launcher")

        response.contentType = "application/zip"
        response.setHeader(
            "Content-Disposition",
            "attachment;filename=sample.zip"
        )
        response.setContentLengthLong(file.length())

        return StreamingResponseBody { outputStream: OutputStream ->
            var bytesRead: Int
            val buffer = ByteArray(2048)
            val inputStream: InputStream = file.inputStream()
            while (inputStream.read(buffer).also { bytesRead = it } != -1) {
                outputStream.write(buffer, 0, bytesRead)
            }
        }
    }
6个回答

5

下载有两种方式:

  1. http.get + msSaveOrOpenBlob(如果已定义)/createObjectURL
  • 您可以完全控制请求、处理和错误。例如,您可以取消请求,添加其他标头等。
  • 下载仍在您的应用程序流中,例如按下F5将取消下载。
  1. 创建隐藏的下载链接并通过编程方式单击它
  • 您不能添加标头,也无法以简单的方式显示进度/错误(有一些技巧涉及BE设置的额外Cookie)
  • 它作为单独的进程启动,例如您可以关闭应用程序选项卡或其他选项卡。

似乎没有混合这两种方法的机会,总的来说,我认为第一种方法更现代化,适用于小文件,但是如果您对此不满意,请尝试第二种方法 (https://dev59.com/6Wgu5IYBdhLWcg3wDS0G#49917066):

function download(url) {
  const a = document.createElement('a')
  a.href = url
  a.download = url.split('/').pop()
  document.body.appendChild(a)
  a.click()
  document.body.removeChild(a)
}

那么这意味着,如果您的请求需要特殊标头,就不可能触发浏览器的本机下载(包括进度条)功能,是吗? - Romain-p
我觉得如果请求是GET,浏览器会处理下载和进度。 - Romain-p
那么作为一种解决方法,您认为以下方案如何:
  • 由于下载URL需要oauth2和某些权限,我实际上无法使用您的第二种方式。但我认为生成动态唯一链接将是解决方案!
  1. 使用oauth和特殊标头请求下载URL
  2. 响应返回一个生成的、一次性可用的链接,用于下载受保护的内容
  3. Angular创建隐藏链接并单击它,因此浏览器下载进度条起作用
- Romain-p
如果您需要一些授权头,单次使用令牌方法是一个不错的选择。我们使用这种方法将下载链接传递给第三方API。 - Petr Averyanov
因为没有人能够证明使用Angular和带有自定义标头的请求触发浏览器下载进度条是可能的,所以我接受你的解决方案,因为它提供了两种可能的文件下载方式。谢谢。 - Romain-p

0

你可以轻松使用这个包

npm i ngx-filesaver

并且

constructor(private _http: Http, private _FileSaverService: FileSaverService) {
}

onSave() {
  this._http.get('yourfile.png', {
    responseType: ResponseContentType.Blob // This must be a Blob type
  }).subscribe(res => {
    this._FileSaverService.save((<any>res)._body, fileName);
  });
}

完整文档在这里 ngx文件保存器文件保存器.js


这里也有同样的问题,浏览器没有显示下载进度。文件只有在完全下载后才可见。 - Romain-p
你确定吗?因为我再次尝试了文件保存器演示,它在浏览器本地下载器中似乎可以正常工作。 - Mohammad Babaei

0
从技术角度来看,您无法更改浏览器在后台接收文件的行为方式。相反,您可以通过将选项observe设置为events计算下载进度,同时进行HTTP请求。这样,您不仅会收到请求的最终响应正文,还可以访问中间的HTTP事件。 之后,您可以显示自己的下载进度,而不是依赖于浏览器。
在Angular中有多种类型的HTTP事件,全部归类为HttpEvent类型。我们还需要明确传递选项reportProgress以接收HttpProgressEvents。HTTP请求最终将如下所示:
this.http.get(url, {
  reportProgress: true,
  observe: 'events',
  responseType: 'blob'
})

您可以在Angular文件下载进度中找到一个很好的实现。


0

试试这个:

$('a#someID').attr({target: '_blank', 
                    href  : 'http://localhost/directory/file.pdf'});

它使用jQuery


0
假设您正在使用Angular的HttpClient,像这样应该可以工作:
    const endpoint = "http://localhost:8080/download/zip"
    const req = new HttpRequest('GET', endpoint, { reportProgress: true, })
    this.Http.request(req).subscribe(event => {
      if (event.type === HttpEventType.DownloadProgress) {
        const percentDone = Math.round(100 * event.loaded / (event.total || 0))
        console.log(percentDone);
      } else if (event instanceof HttpResponse) {
        const blob = new Blob([event.body as any], { type: 'application/zip' });
        window.location.href = URL.createObjectURL(blob);
      }
    })

如果我理解正确的话,@Romain-p,您无法访问浏览器本地下载区域。您可以将console.log替换为自己的代码,以使用百分比更新页面HTML。 - Emmanuel
确实如此,但这不是期望的行为。我想使用本地下载区域。 - Romain-p
1
@Romain-p,这是不可能的。 - Emmanuel
我是说,如果我想要为下载进度创建自己的UI,也许我不会问这个问题?这个问题并不难,就是如何使用Angular来使用导航器本地下载UI :) - Romain-p
@Emmanuel 在这里是正确的,尝试定制浏览器本地下载功能并不是解决这个问题的实际方法。建议扩展您自己的用户界面以显示下载进度。 - Nathan Beck
显示剩余4条评论

-1

1
再次强调,这种方法不会触发本地下载Chrome小部件(带有进度条等)。 - Romain-p

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