4

So, when I request my webservice for getting download a zip file, it downloads the file content secretely and all of a sudden, the file appears in the download task bar but already downloaded full (100%)

Using the following angular method:


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

So here is how I am subscribing:

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

So I noticed my console.log(...) isn't called until the end of the download, so I suppose that the browser-ui can't detect the download until it reaches window.location.href.

How to force the download to be shown in the download task bar before the end of the transfert, and watch the download progress in the browser? I coudld not find anything related to async blop or something like that.

PS: my backend is serving a stream of data, so the backend is not the problem. When calling my api directly through the browser, we can see the download progress in the download task bar. Still, if you guys are interested, this is the snippet (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)
            }
        }
    }
Emmanuel
  • 4,933
  • 5
  • 46
  • 71
Romain-p
  • 518
  • 2
  • 9
  • 26

6 Answers6

5

For download you have 2 ways:

  1. http.get + msSaveOrOpenBlob (if its defined)/createObjectURL
  • you have full control over request, process and errors. E.g. u can cancel request, add additional headers, etc.
  • download remains in your app flow, e.g. F5 will cancel it.
  1. create hidden download link and click it programatically
  • you cannot add headers, you cannot show progress/errors in simple way (there are some tricks involving additional cookies to set by BE)
  • Its strated as separate process, e.g. you can close your app tab or whatever.

There seems to be no chance of mixing these 2 approaches and in general I would say 1st one is more modern and preferable for small files, however if you are not happy with it try 2nd one (https://stackoverflow.com/a/49917066/4019404):

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)
}
Petr Averyanov
  • 9,327
  • 3
  • 20
  • 38
  • So.. that means it's not possible to trigger the native download (+progress bar included I mean) functionnality of the broswer if your request requires special headers right? – Romain-p Dec 19 '21 at 16:47
  • I feel like the broswer will handle the download + progress only if the request is a GET – Romain-p Dec 19 '21 at 16:52
  • So as a workaround, what do you think about the following: - Since the download URL requires an oauth2 & some permissions, I can't actually use your 2nd way. But I think generating dynamic unique links would be the solution! 1. Request the download URL with oauth & special headers 2. Response returns a generated & one-time usable link for downloading the protected content 3. Angular creates the hidden link & click it and so the broswer download progress bar works – Romain-p Dec 19 '21 at 17:06
  • Yes, if you need some auth-headers, single-use token approach is a good option. We use this to pass download link to 3rd party API. – Petr Averyanov Dec 19 '21 at 22:24
  • So since none have been able to proof that it is possible to trigger the browser download's progress bar with angular and requests with custom headers, I accept yours since it gives the 2 possibles ways for downloading a file. Thanks – Romain-p Dec 21 '21 at 19:50
0

Assuming you are using Angular's HttpClient, something like this should work:

    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);
      }
    })
Emmanuel
  • 4,933
  • 5
  • 46
  • 71
  • well, thanks for your time but I'm asking for the download to be shown in the broswer native download page. All you are doing here is logging the download progress – Romain-p Dec 13 '21 at 16:32
  • @Romain-p If I understand you correctly, you can't access the browser native download area. You can replace the console.log with your own code to update your page html with the percent. – Emmanuel Dec 14 '21 at 04:56
  • Indeed, but this is not the desired behaviour. I'd like to use the native download area – Romain-p Dec 14 '21 at 21:06
  • 1
    @Romain-p That is not possible – Emmanuel Dec 15 '21 at 01:15
  • I'd suggest you just show your own progress indicator while the download is in progress, and remove once finished. You could even mimic the look of the native download aread. I'm not sure why this is the desired behaviour? – Emmanuel Dec 15 '21 at 01:16
  • I mean if I wanted to create my own ui for the download progress, I would not have asked this question maybe? The question isn't that hard, how to use the navigator native download ui using angular :) – Romain-p Dec 16 '21 at 23:44
  • @Emmanuel is correct here, trying to customize the browsers native download functionality is not a practical approach to this problem. Suggest extending your own UI to show download progress. – Nathan Beck Dec 17 '21 at 20:37
  • @NathanBeck I'm not trying to cuztomize the native download functionnality of the browser, I just try to use it. There's no reason to implement your own progress-bar if the broswer already does it. – Romain-p Dec 19 '21 at 16:40
  • @Romain-p best of luck – Nathan Beck Dec 20 '21 at 20:21
0

You can simply use this package

npm i ngx-filesaver

and

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

complete doc here ngx file saver and also file saver.js

Mohammad Babaei
  • 473
  • 6
  • 16
  • Same problem here, the download progress is not shown by the browser. The file is visible once it's totally downloaded – Romain-p Dec 19 '21 at 16:38
  • Are you sure about this? because I tasted file saver demo again and it seems working fine for me in browser native downloader – Mohammad Babaei Dec 19 '21 at 17:57
0

From a technical point of view, you can't change the way browsers behave for receiving a file in the background. Instead, you can calculate the download progress by setting the option observe to events while making an HTTP request. In this way, you won't just receive the final response body of the request but also get access to intermediate HTTP events. After that, you can show your own download progress apart from the browser.

There are multiple kinds of HTTP events in Angular, all consolidated under the type HttpEvent. We also need to explicitly pass the option reportProgress in order to receive HttpProgressEvents. The HTTP request will eventually look like follows:

this.http.get(url, {
  reportProgress: true,
  observe: 'events',
  responseType: 'blob'
})

You can find a good implementation at angular file download progress.

Amirhossein Mehrvarzi
  • 18,024
  • 7
  • 45
  • 70
0

Try this:

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

It uses jQuery

Hermanboxcar
  • 418
  • 1
  • 13
-1

User package https://www.npmjs.com/package/file-saver

npm i file-saver

and then in your component, import the SaveAs method

import { saveAs } from 'file-saver';


saveAs(url, name);
mak15
  • 367
  • 2
  • 12