Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Upload multiple files in one request #671

Open
Salma7amed opened this issue Mar 14, 2017 · 28 comments
Open

Upload multiple files in one request #671

Salma7amed opened this issue Mar 14, 2017 · 28 comments

Comments

@Salma7amed
Copy link

What I noticed is that if I upload multiple files at once. The uploader performs multiple requests to the url for each single file. So is it possible to receive all the files in one request ?

@vijj
Copy link

vijj commented Mar 24, 2017

multi fileupload by using flowJs multiple request fine depends on fileFize. can any one ng2-file-upload problem on multiplefile.

@rbasniak
Copy link

rbasniak commented Apr 2, 2017

I noticed this too, the .uploadAll() method does a single request for every file in queue.

@Salma7amed Did have any sucess on uploading all files in a single request?

@Salma7amed
Copy link
Author

@rbasniak not using this library.

@rbasniak
Copy link

rbasniak commented Apr 5, 2017

@Salma7amed What library are you using now?

@Salma7amed
Copy link
Author

@rbasniak I used FormData to perform a post request with the files.

@ClaytonBrawley
Copy link

The file that uploads the attached files in ngx-uploader.ts has a function that it uses called uploadFilesInQueue(). What this function does is loop through the queue and sends each file off to uploadFile() to be sent off using a XMLHttpRequest. What you can do to send all files at one time is, instead of calling uploadFile() for each file in the queue, add each file to the FormData form before sending it off. Something like this:

uploadFilesInQueue(): void {
    if (this.getQueueSize() === 1) {
      this.uploadFile(this._queue[0]);
    } else if (this.getQueueSize() > 1) {
      this.uploadAllFiles();
    }
  }

uploadAllFiles(): void {
    const xhr = new XMLHttpRequest();
    const form = new FormData();

    for (const file of this._queue) {
      form.append(this.opts.fieldName, file, file.name);
    }

    xhr.send(form);
  };

Just like @Salma7amed said above.

@josecarlosaparicio
Copy link

Finally, I was inspired thanks to @ClaytonBrawley and can solve this issue extending FileUploader class:

import { FileUploader, FileItem, FileUploaderOptions } from 'ng2-file-upload';

export class FileUploaderCustom extends FileUploader {

  constructor(options: FileUploaderOptions) {
    super(options);
  }

  uploadAllFiles(): void {

    var xhr = new XMLHttpRequest();
    var sendable = new FormData();
    var fakeitem: FileItem = null;
    this.onBuildItemForm(fakeitem, sendable);

    for (const item of this.queue) {
      item.isReady = true;
      item.isUploading = true;
      item.isUploaded = false;
      item.isSuccess = false;
      item.isCancel = false;
      item.isError = false;
      item.progress = 0;

      if (typeof item._file.size !== 'number') {
        throw new TypeError('The file specified is no longer valid');
      }
      sendable.append("files", item._file, item.file.name);
    }

    if (this.options.additionalParameter !== undefined) {
      Object.keys(this.options.additionalParameter).forEach((key) => {
        sendable.append(key, this.options.additionalParameter[key]);
      });
    }

    xhr.onload = () => {
      var gist = (xhr.status >= 200 && xhr.status < 300) || xhr.status === 304 ? 'Success' : 'Error';
      var method = 'on' + gist + 'Item';
      this[method](fakeitem, null, xhr.status, null);

    };
    xhr.onerror = () => {
      this.onErrorItem(fakeitem, null, xhr.status, null);
    };

    xhr.onabort = () => {
      this.onErrorItem(fakeitem, null, xhr.status, null);
    };

    xhr.open("POST", this.options.url, true);
    xhr.withCredentials = true;
    if (this.options.headers) {
      for (var _i = 0, _a = this.options.headers; _i < _a.length; _i++) {
        var header = _a[_i];
        xhr.setRequestHeader(header.name, header.value);
      }
    }
    if (this.authToken) {
      xhr.setRequestHeader(this.authTokenHeader, this.authToken);
    }
    xhr.send(sendable);
  };

}

Then, in the component can be use like this:

uploader: FileUploaderCustom;

ngOnInit() {
    this.uploader = new FileUploaderCustom ({
        url: urlSubirMiniatura
    });
}

uploadAllFiles(){
   this.uploader.uploadAll();
}

Notice that all files are appending to sendable with the name "files", so it could be refactored better.

@varrob112
Copy link

varrob112 commented Jul 16, 2017

@josecarlosaparicio :
uploadAllFiles(){
this.uploader.uploadAll();
}
You mean this.uploader.uploadAllFiles(); ?

@dannyhchan
Copy link

@josecarlosaparicio :
onSuccess does not read the response if server is going to return a json object. Any way to fix this?

@Max053
Copy link

Max053 commented Aug 15, 2017

Is this 1 file per request the expected behaviour from the beginning, or would a PR be accepted if it were to fix this behaviour (maybe with an optional setter)?

@dannyhchan
Copy link

1 file per request is working as expected. I think with an option setter for upload all rather than per file would be good. What I have as a workaround is all on the server end by submitting the total queue count and along with the files and handling it with the 1 file per upload scenario. If the entire batch of files can be submitted at one, this could be avoided.

@judsonmusic
Copy link

judsonmusic commented Aug 18, 2017

Big TIME +1
I love the plugin but its steers away from server performance by doing multiple requests. I would love to be able to send all of the file in an array at once and loop through the file array
files.map(obj)=>{ fs.writeFIle... }

I am not a big fan of extending(hacking) other people's work, "Patience is a Virtue" :)

@Max053
Copy link

Max053 commented Aug 20, 2017

@judsonmusic I'm currently working on this feature, I will open up a PR as soon as it's finished :)

@adrianfaciu
Copy link

We're trying to get this into a better shape so if you still have that PR it would be awesome 😉

@ClaytonBrawley
Copy link

If no one gets to this I can try and throw something together if more details are provided.

@adrianfaciu
Copy link

@ClaytonBrawley you can assign this to yourself and see what you can done. Once you have something create a PR and we can all have a look.

@andrew-starosciak
Copy link

andrew-starosciak commented Oct 13, 2017

@dannyhchan See below for a workaround on firing the onCompleteItem callback.

import {FileItem, FileUploader, FileUploaderOptions} from 'ng2-file-upload';

export class FileUploaderCustom extends FileUploader {

    constructor(
        options: FileUploaderOptions
    ) {
        super(options);
    }

    uploadAllFiles(): void {
        // const _this = this;
        const xhr = new XMLHttpRequest();
        const sendable = new FormData();
        const fakeItem: FileItem = null;
        this.onBuildItemForm(fakeItem, sendable);

        for (const item of this.queue) {
            item.isReady = true;
            item.isUploading = true;
            item.isUploaded = false;
            item.isSuccess = false;
            item.isCancel = false;
            item.isError = false;
            item.progress = 0;

            if (typeof item._file.size !== 'number') {
                throw new TypeError('The file specified is no longer valid');
            }
            sendable.append('files[]', item._file, item.file.name);
        }

        if (this.options.additionalParameter !== undefined) {
            Object.keys(this.options.additionalParameter).forEach((key) => {
                sendable.append(key, this.options.additionalParameter[key]);
            })
        }

        xhr.onerror = () => {
            this.onErrorItem(fakeItem, null, xhr.status, null);
        }

        xhr.onabort = () => {
            this.onErrorItem(fakeItem, null, xhr.status, null);
        }

        xhr.open('POST', this.options.url, true);
        xhr.withCredentials = true;
        if (this.options.headers) {
            for (let _i = 0, _a = this.options.headers; _i < _a.length; _i++) {
                const header = _a[_i];
                xhr.setRequestHeader(header.name, header.value);
            }
        }
        if (this.authToken) {
            xhr.setRequestHeader(this.authTokenHeader, this.authToken);
        }

        xhr.onload = () => {
            const headers = this._parseHeaders(xhr.getAllResponseHeaders());
            const response = this._transformResponse(xhr.response, headers);
            const gist = this._isSuccessCode(xhr.status) ? 'Success' : 'Error';
            const method = '_on' + gist + 'Item';
            for (const item of this.queue) {
                this[method](item, response, xhr.status, headers);
            }
            this._onCompleteItem(this.queue[0], response, xhr.status, headers);
        }

        xhr.send(sendable);
    }
}

@drdreo
Copy link

drdreo commented Nov 25, 2017

Ran into the same Issue. Ended up rewriting my process function on the server to handle one item per call and not all at once. Annoying because i can't bulk further requests.

@a-morn
Copy link

a-morn commented Jan 24, 2018

@adrianfaciu Is anyone working on this? I could do a PR otherwise

@adrianfaciu
Copy link

As far as I know, no, there is no open PR for this.

@koenvanderlinden
Copy link

koenvanderlinden commented Mar 17, 2018

@adrianfaciu @a-morn I create a PR #993 for a multiupload in one reqeust. It probably needs some refactoring. Let me know what you think of it.

@eikishi01
Copy link

I was just looking for a way to upload all files individually and I got to this threat finding out that it is the default behaviour, which is what I needed @koenvanderlinden does you PR #993 consider allowing the use of both approaches? both use cases are valid and it should count for them.

@koenvanderlinden
Copy link

@eikishi01 you could use both. The way how multiple upload is done is based on configuration of the upload component.

@TianrenWang
Copy link

@andrew-starosciak
Can you explain the reasoning behind doing a for loop on each item in the queue? With the for loop you receive multiple responses from the uploaded server, when only one response is necessary.

@sridharan31
Copy link

this.uploader.clearQueue(); onsucess file not removed get error

mandateCancelComponent.html:786 ERROR TypeError: Cannot read property 'abort' of undefined
    at FileUploaderCustom.push../node_modules/ng2-file-upload/file-upload/file-uploader.class.js.FileUploader.cancelItem (file-uploader.class.js:112)
    at FileItem.push../node_modules/ng2-file-upload/file-upload/file-item.class.js.FileItem.cancel (file-item.class.js:38)
    at FileUploaderCustom.push../node_modules/ng2-file-upload/file-upload/file-uploader.class.js.FileUploader.removeFromQueue (file-uploader.class.js:85)
    at FileItem.push../node_modules/ng2-file-upload/file-upload/file-item.class.js.FileItem.remove (file-item.class.js:41)
    at Object.eval [as handleEvent] (mandateCancelComponent.html:795)
    at handleEvent (core.js:28969)
    at callWithDebugContext (core.js:30039)
    at Object.debugHandleEvent [as handleEvent] (core.js:29766)
    at dispatchEvent (core.js:19631)
    at core.js:28178

@ghost
Copy link

ghost commented Mar 30, 2020

@andrew-starosciak

i think this works for multiple files :

xhr.onload = () => {
      const headers = this._parseHeaders(xhr.getAllResponseHeaders());
      const response = this._transformResponse(xhr.response, headers);
      const gist = this._isSuccessCode(xhr.status) ? "Success" : "Error";
      const method = "_on" + gist + "Item";
      const queueLength = this.queue.length;
      for (var i = 0; i < queueLength; i++) {
        this[method](
          this.queue[this.queue.length - 1],
          response,
          xhr.status,
          headers
        );
        this._onCompleteItem(
          this.queue[this.queue.length - 1],
          response,
          xhr.status,
          headers
       );
     }   
 };

@costeacosmin92
Copy link

costeacosmin92 commented Apr 16, 2020

Hi everyone,
I am using this plugin with Angular 9, and Spring Boot on backend, and was asked to create a questionnaire where for every item on the checklist the user can upload one or more pictures. My trouble was the same as the OP's, and ended up getting FileItem[] array from the 'uploader', and putting each file on a Zip file using JSZip library.

import { FileUploader, FileUploaderOptions, FileItem } from 'ng2-file-upload';
import * as JSZip from 'jszip';

async onFormSubmit() {
      let zipFile: JSZip = new JSZip();

      let items: FileItem[] = this.uploader.getNotUploadedItems().filter((item: FileItem) => 
           !item.isUploading);

      items.forEach(item  =>{
          zipFile.file(item.file.name, item.file.rawFile, {base64: true});
      })

      let finalZip = await zipFile.generateAsync({type:"blob", compression: "DEFLATE"});

     // now you can send 'finalZip' to backend using common HttpClient .
}

Hope it can be useful.

@SohrabRo
Copy link

@josecarlosaparicio your solution works like a charm but the progress bar is set to 0 and it is never updated! Therefore the progress bar does not work. Any solution about this problem you may think of?

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Projects
None yet
Development

No branches or pull requests