1

I'm trying to create a progress bar when uploading files via Angular 6 (frontend) and NodeJs (backend) to an asw-s3 bucket. How to fetch the progress (or the already uploaded bytes) and receive them in the Angular 6 frontend in realtime?

Florian Ludewig
  • 4,338
  • 11
  • 71
  • 137

2 Answers2

2

Not sure if angular has a specific method for this, but here is a working example I used in some of my angular based websites :

sendFile(file) {
    let formData: FormData = new FormData();
    formData.append('my_file', file);

    let xhr = new XMLHttpRequest();
    xhr.upload.onprogress = function (progEvent: ProgressEvent) {
        if (progEvent.lengthComputable) {
            var uploadedSoFar = (progEvent.loaded / progEvent.total) * 100;
            console.log("Uploaded: " + uploadedSoFar + "% ")
            if (progEvent.loaded == progEvent.total){
                // uploaded up to 100%
            }
        }

    };
    xhr.open("POST", `/your_site`, true);
    xhr.send(formData);
}

Some explanation of what is going on :

FormData

The FormData interface provides a way to easily construct a set of key/value pairs representing form fields and their values, which can then be easily sent using the XMLHttpRequest.send() method. It uses the same format a form would use if the encoding type were set to "multipart/form-data"

https://developer.mozilla.org/en-US/docs/Web/API/FormData

XMLHttpRequest

Use XMLHttpRequest (XHR) objects to interact with servers. You can retrieve data from a URL without having to do a full page refresh. This enables a Web page to update just part of a page without disrupting what the user is doing.

https://developer.mozilla.org/en-US/docs/Web/API/XMLHttpRequest

Node Side I'm updating this post to add a node code sample (after the comments). I am not as good in node.js, so my following code is not a good example.

const http = require('http');

const hostname = '127.0.0.1';
const port = 3000;

const server = http.createServer((req, res) => {
  // Remove this, I use it for this example to be easy to reproduce
  res.setHeader('X-Frame-Options', 'ALLOWALL');
  res.setHeader('Access-Control-Allow-Origin', '*');
  res.setHeader('Access-Control-Allow-Methods', 'POST, GET');
  res.setHeader('Access-Control-Allow-Headers', 'Origin, X-Requested-With, Content-Type, Accept');
  if (req.method == 'POST') {
        console.log("Receiving file");
        var body = '';
        req.on('data', function (data) {
            body += data;
            console.log("receiving data : " + body);
        });
        req.on('end', function () {
            console.log("received all the data: " + body);
        });
        res.writeHead(200, {'Content-Type': 'text/html'});
        res.end('Reception completed');
    }

});

server.listen(port, hostname, () => {
  console.log(`Server running at http://${hostname}:${port}/`);
});

If you receive data in your node.js, this would mean that your front is working correctly.

madjaoue
  • 5,104
  • 2
  • 19
  • 31
  • This really helps! But how would you extract the file from the formData in the backend? – Florian Ludewig Jul 04 '18 at 15:42
  • Depends on your backend. in django, you can extract the file from the request : `my_file = request.FILES['my_file']`. If you want to store it, if you're in python backend : `fs = FileSystemStorage(); fs.save(filename, my_file)` – madjaoue Jul 04 '18 at 15:45
  • I am getting an `undefined` when accessing `req.FILES['file']` – Florian Ludewig Jul 04 '18 at 16:09
  • What is your backend ? – madjaoue Jul 04 '18 at 16:20
  • I am using NodeJs – Florian Ludewig Jul 04 '18 at 16:22
  • maybe try to `console.log( req.files)`. here's a post that gives more details on receiving a file through xhr on node.js : https://stackoverflow.com/questions/33669500/receiving-file-in-node-express-uploaded-with-xhr – madjaoue Jul 04 '18 at 16:22
  • I have added the `multipartMiddleware` but `req.files` does return an empty object `{}`, also `req.files['file']` is again `undefined` – Florian Ludewig Jul 04 '18 at 16:32
  • I updated my answer, please tell me if it fixes your problem – madjaoue Jul 05 '18 at 09:33
  • The post route executes, but the `req.on('data')` isn't called. – Florian Ludewig Jul 05 '18 at 11:15
  • I managed to fetch the file data via `busboy`, and the upload to the s3 bucket works, too. But the progress is instantly at 100% even if the file upload finished many seconds later. – Florian Ludewig Jul 05 '18 at 11:31
  • I edited my post to make it more compatible, and tested it. Please try it in this format and tell me if this works. One other explanation could be that the file you are transferring is too small, in this case try with a "slow 3g connection". You have this option in chrome performance toolbox. – madjaoue Jul 05 '18 at 12:28
  • `uploadedSoFar` is still 100% instantly and I am not able to find the setting for another connection http://prntscr.com/k2vtm3 – Florian Ludewig Jul 05 '18 at 13:17
  • I may have found the issue... The problem might occur because I am hosting the server locally... I will notify you in a few minutes – Florian Ludewig Jul 05 '18 at 13:22
  • The progress percentage works fine when running the server not locally. – Florian Ludewig Jul 05 '18 at 13:56
  • But I still have one problem... the percentage is correctly logged but does not update on the page: `xhr.upload.onprogress = (progEvent: ProgressEvent) => { if (progEvent.lengthComputable) { this.uploadedPercentage = (progEvent.loaded / progEvent.total) * 100; // uploadedPercentage does not update in the html console.log(this.uploadedPercentage + '%'); // this log is outputted correctly if (progEvent.loaded == progEvent.total) { this.uploadedPercentage = 100; } }` I use the progress in the html like: `{{uploadedPercentage}}` – Florian Ludewig Jul 05 '18 at 13:58
  • I can't think of a reason other than "the transfer is really fast, faster than changeDetection refreshing time.". But this is a bit detached from the transfer problem per say. What I can advise is to read this post about "triggering detection changes": https://stackoverflow.com/questions/34827334/triggering-change-detection-manually-in-angular Good luck ;) ! – madjaoue Jul 05 '18 at 14:10
  • okay, I will try it... something interesting to note is, that the progress updates when I am clicking somewhere on the page – Florian Ludewig Jul 05 '18 at 14:17
  • I believe that detection change fires when an event occurs. You're welcome ;) – madjaoue Jul 05 '18 at 14:18
2

you can just upload a file with the regular HttpClient and use the flag reportProgress: true.

Full example:

constructor(private _http: HttpClient,
            private _logger: Logger) {}

this._http
    .post(FILE_UPLOAD_URL, formData, {
          reportProgress: true,
          observe: 'events'
        })
        .subscribe((event: HttpEvent<any>) => {
            switch (event.type) {
                case HttpEventType.Sent:
                    this._logger.debug('Upload started');
                    break;
                case HttpEventType.DownloadProgress:
                    // Live stats are also possible for downloads
                case HttpEventType.UploadProgress:
                    if (event.total) {
                         const progress = Math.round(event.loaded / event.total * 100);
                         const timeElapsed = Date.now() - startTime;
                         const uploadSpeed = event.loaded / (timeElapsed / 1000);
                         const uploadTimeRemaining = Math.ceil(
                                            (event.total - event.loaded) / uploadSpeed
                                        );
                         const uploadTimeElapsed = Math.ceil(timeElapsed / 1000);
                         const uploadSpeed = uploadSpeed / 1024 / 1024;
                         this._logger.debug('Upload stats:', progress, timeElapsed, uploadSpeed, uploadTimeRemaining,  uploadTimeElapsed, uploadSpeed);

                         break;
                     case HttpEventType.Response:
                          this.progressForFile = 100;
                          this._logger.debug('Done! ResponseBody:', event.body);

                 });

If hope, this helps! :-)

user7646788
  • 116
  • 1
  • 2
  • This really helped! Unfortunately, the progress does not track the upload progress. It's almost instantly 100% and the upload finishes seconds later. – Florian Ludewig Jul 05 '18 at 11:30