Angular 4 - multipart support in HttpClient

6,682 views
Skip to first unread message

nicolas duminil

unread,
Sep 6, 2017, 11:40:50 AM9/6/17
to Angular and AngularJS discussion
Hello,

From an Angular component, I need to call a JAX-RS service having this signature:

  @POST

  @Path("/import")

  @Produces(MediaType.APPLICATION_JSON)

  @Consumes(MediaType.MULTIPART_FORM_DATA)

  public Response uploadExcelFile(FormDataMultiPart form) throws Exception


I need to pass in a multi-part having two parts:a file to be uploaded and a context as a POJO. I wrote a JAX-RS (Jersey) client allowing me to test the service, here is the code:


    FileDataBodyPart filePart = new FileDataBodyPart("excel-files", new File("…"));

    ImportContext ic = new ImportContext(…);

    MultiPart multipart = new FormDataMultiPart().field("import-context", ic, MediaType.APPLICATION_JSON_TYPE).bodyPart(filePart);

    multipart.setMediaType(MediaType.MULTIPART_FORM_DATA_TYPE);

    resource.type("multipart/form-data").post(Response.class, multipart);


Calling my service this way works as expected and produces the following payload:


--Boundary_1_1795960102_1504711197604

Content-Type: application/json

Content-Disposition: form-data; name="import-context"


{"createMissingBundles":"true","createMissingMessages":"true","importMode":"CREATE_AND_UPDATE","multiple":"false"}

--Boundary_1_1795960102_1504711197604

Content-Type: application/octet-stream

Content-Disposition: form-data; filename="exportTranslations.xls"; modification-date="Wed, 06 Sep 2017 12:49:07 GMT"; size=35840; name="excel-files"

 

Now, I need to do the same in Angular but I don't find how. I have a callback in which I enter with an event having a FormData which already contains the file to be uploaded. I need to add the context object. If I do that:


public onBeforeSend(event: any) {
this.importContext = new ImportContext(...);
event.formData.append("import-context", JSON.stringify(this.importContext));
}

I have the following payload:

------WebKitFormBoundaryTB8snRJsZf3E31Vp Content-Disposition: form-data; name="excel-files"; filename="exportTranslations.xls" Content-Type: application/vnd.ms-excel ------WebKitFormBoundaryTB8snRJsZf3E31Vp Content-Disposition: form-data; name="import-context" {"multiple":false,"createUpdate":null,"createMissingBundles":false,"createMissingMessages":false} ------WebKitFormBoundaryTB8snRJsZf3E31Vp--


and the following exception on the server side:


java.lang.IllegalArgumentException: No available MessageBodyReader for class com.coface.corp.translationView.utils.imports.ImportContext and media type text/plain

at com.sun.jersey.multipart.BodyPart.getEntityAs(BodyPart.java:307)

        ...............


Obviously, not giving any MediaType to the "import-context" part above, takes text/plain by defaut, hence the exception. After having googled a while, I found out that I could specify the part's MediaType through a blob, like this:

event.formData.append("import-context", new Blob([JSON.stringify(this.importContext)], { type: "application/json"}));

Doing that produces the following payload:

------WebKitFormBoundary8VRwVcqKrGBn363A Content-Disposition: form-data; name="excel-files"; filename="exportTranslations.xls" Content-Type: application/vnd.ms-excel ------WebKitFormBoundary8VRwVcqKrGBn363A Content-Disposition: form-data; name="import-context"; filename="blob" Content-Type: application/json ------WebKitFormBoundary8VRwVcqKrGBn363A--

There is no any more exception in this case, however, there is no any data in the blob and, consequently, the correponding parameter on the server side is null.

So, after hours of work, I didn't find any way to produce the Angular coide equivalent to a simple Java client and I would be very obligated to anybody who could help.

As a personal comment, I would like to add that there is such a pain to switch to JavaScript, where everything seems so damn inexplicit and undocumented, when comming from Java where everything is so clear and specs based.

Kind regards,
Nicolas

nicolas duminil

unread,
Sep 11, 2017, 8:46:31 AM9/11/17
to Angular and AngularJS discussion
Does anyone fell concerned please ?

Sander Elias

unread,
Sep 11, 2017, 11:02:28 AM9/11/17
to Angular and AngularJS discussion

nicolas duminil

unread,
Sep 14, 2017, 11:49:21 AM9/14/17
to Angular and AngularJS discussion
Hi Sander,

Thanks for your reply. Looking at this link, I don't see any relation with the issue I posted. Could you please explain ?

Many thanks in advance,

Nicolas

Sander Elias

unread,
Sep 14, 2017, 9:14:48 PM9/14/17
to Angular and AngularJS discussion
Hi Nicolas,

Sorry, It seems I ended up answering the wrong question ;)
It looks like your ImportContent class does not provide a proper toJson, or toString method. Did you try logging out  `JSON.stringify(this.importContext)` to the console?

Regards
Sander

nicolas duminil

unread,
Sep 15, 2017, 5:21:15 AM9/15/17
to Angular and AngularJS discussion
Hi Sander,

Yes, this is the ouput value:

ImportInputContext {multiple: false, createUpdate: NaN, createMissingBundles: false, createMissingMessages: false}

Do you see anything wrong here ?

Many thanks in advance for your help.

Nicolas

Sander Elias

unread,
Sep 15, 2017, 5:31:27 AM9/15/17
to Angular and AngularJS discussion
Hi Nicolas,

That is the output from JSON.stringyfy? Doesn't look like valid JSON.

Regards
Sander

nicolas duminil

unread,
Sep 15, 2017, 5:34:42 AM9/15/17
to Angular and AngularJS discussion
Sorry, the stringified value is:

{"multiple":false,"createUpdate":null,"createMissingBundles":false,"createMissingMessages":false}

Sander Elias

unread,
Sep 15, 2017, 6:08:13 AM9/15/17
to Angular and AngularJS discussion
That looks better. should work, hold on..

event.formData.append("import-context", new Blob([JSON.stringify(this.importContext)], { type: "application/json"}),"import-context");

I think that should work. You forgot to add the filename to the part.
You realize none of this is connected to angular at all? ;)

Regards
Sander

nicolas duminil

unread,
Sep 15, 2017, 8:04:45 AM9/15/17
to Angular and AngularJS discussion
Hi Sander,

Thanks again for all this trouble. I made this modification so I replaced

event.formData.append("import-context", new Blob([JSON.stringify(this.importContext)], { type: "application/json"}));

by
event.formData.append("import-context", new Blob([JSON.stringify(this.importContext)], { type: "application/json"}),"import-context");

The HTTP payload is as follows:

------WebKitFormBoundary8KpdAMW6dgaX8oBA Content-Disposition: form-data; name="excel-files"; filename="exportTranslations.xls" Content-Type: application/vnd.ms-excel ------WebKitFormBoundary8KpdAMW6dgaX8oBA Content-Disposition: form-data; name="import-context"; filename="import-context" Content-Type: application/json ------WebKitFormBoundary8KpdAMW6dgaX8oBA--

As you can see, this doesn't change much. Previously, without using the filename as a parameter, the Blob constructor generated a default file name of "Blob". Now, explicitly specifying it, the filename is "import-context", but the content of the Blob is still empty.

Would you see anything else I could do such that to get things working ?

Many thanks in advance,

Nicolas

Sander Elias

unread,
Sep 15, 2017, 8:49:24 AM9/15/17
to Angular and AngularJS discussion
Hi Nicolas,

Can you assign the blob to a var and inspect it? something strange is happening. Did you try it with a raw sting yet?
here is a snippet on how to log a blob!
var myReader = new FileReader();
myReader
.onload = function(event){ console.log(JSON.stringify(myReader.result)); };
myReader
.readAsText(myBlob);

like:
event.formData.append("import-context", new Blob([`{"multiple":false,"createUpdate":null,"createMissingBundles":false,"createMissingMessages":false}`], { type: "application/json"}),"import-context");

Hope this helps a bit.
Regards
Sander

nicolas duminil

unread,
Sep 15, 2017, 9:09:08 AM9/15/17
to Angular and AngularJS discussion
Hi Sander,

Yes, it helps a lot, thanks.

I inserted this code:

var myReader = new FileReader();
myReader.onload = function (event) { console.log("myReader.result",JSON.stringify(myReader.result)); };
myReader.readAsText(new Blob([JSON.stringify(this.importContext)], { type: "application/json" }), "import-context");
It displays:

myReader.result "{\"multiple\":false,\"createUpdate\":null,\"createMissingBundles\":false,\"createMissingMessages\":false}"

As for using a raw string I did it with exactly the same result.

Many thanks for your help and support.

Nicolas

Sander Elias

unread,
Sep 15, 2017, 9:36:05 AM9/15/17
to Angular and AngularJS discussion
Hi Nicolas,

myReader.result "{\"multiple\":false,\"createUpdate\":null,\"createMissingBundles\":false,\"createMissingMessages\":false}"
Hmm, that doesn't look right. O wait it does, as you are stringify's the bare string tin your console.log. 
Can you try a different browser?  Is there perhaps a (faulty) polyfill loaded for formData? I have done this before, without issues, so it should work for you too.

Regards
Sander

nicolas duminil

unread,
Sep 15, 2017, 9:43:40 AM9/15/17
to Angular and AngularJS discussion
Hi Sander,

Have tried with Firefox and it displays the same:

myReader.result "{\"multiple\":false,\"createUpdate\":null,\"createMissingBundles\":false,\"createMissingMessages\":false}"

Could you please develop on "as you are stringify's the bare string" ? I don't know what a polyfill for formData might be.

Thanks,

Nicolas

Sander Elias

unread,
Sep 15, 2017, 9:50:51 AM9/15/17
to Angular and AngularJS discussion
Hmm, the myReader.result is already a string, so stringifying it doesn't make sense

nicolas duminil

unread,
Sep 15, 2017, 9:59:30 AM9/15/17
to Angular and AngularJS discussion
Not stringifying it, like this:

myReader.readAsText(new Blob([this.importContext], { type: "application/json" }));
gives:

myReader.result "[object Object]"

Sander Elias

unread,
Sep 15, 2017, 11:26:44 AM9/15/17
to Angular and AngularJS discussion
no, that's the wrong stringify you remove ;)
Reply all
Reply to author
Forward
0 new messages