mgonto/restangular

multipart form data submit with restangular

zoheb opened this issue · 27 comments

I have to submit a form which includes a user selected file to the server. I can get this working using $http directly as below,

    var fd = new FormData();
            fd.append("profile",angular.toJson(profile));
             fd.append("file", file);
            return $http.post("/server/api/profile/me/bio", fd, {
                withCredentials: true,
                headers: {'Content-Type': undefined },
                transformRequest: angular.identity
            });

I tried to do the same submit using restangular as below
return userAPI.one('me').customPOST(fd,"bio",{}, {'Content-type':undefined});

However this doesn't do the multipart submit correctly and attempts to send a empty payload request. I suspect this is because I need to specify the transformRequest flag to address this issue: angular/angular.js#1587

Is there a way to do set a request transformer just on this request?

Thanks

Hey,

You can do that in the latest version of Restangular

userAPI.one('me').withHttpConfig({transformRequest: angular.identity}).customPOST(...`

That should work for you :).

Also, all other $httpConfigurations can be set in that withHttpConfig call as a parameter.

Please tell me if it worked and reopen if it didn't

Thanks for the reply.

I updated to the latest version and changed to use withHttpConfig as you mentioned, but it still didn't quite work. Now the request payload is not empty but has the string "[object Object]". Note that the object I am passing in ("fd") is a FormData object.

Did you set there the withCredentials and contentType as well?

It should be the same with those.

Can you debug https://github.com/mgonto/restangular/blob/master/src/restangular.js#L457 and check what's being sent in to $http? You can compare that to see what's going on.

Everything seems to be the same except the "data" attribute shows up in the debugger as "Object" instead of "FormData" when I used $http directly and put a breakpoint in angular.js. Since it's no longer a FormData object I think $http skips all the multipart handling stuff.

I wasn't able to figure out where the passed in FormData object was being changed in the restangular codebase. When a formdata object is passed in think it should just preserve it.

Typo - I meant is shows up as "Object" when using restangular, but shows up correctly as FormData object when using $http

I know what it's. The problem is that I'm stripping Restangular stuff. As a hack go to elemFunction and remove StripRestangular part. 

If that works Ill get it fixed :) 


Mobile mail. Excuse brevity and typos.

On Thu, Nov 21, 2013 at 8:28 PM, zohebsait notifications@github.com
wrote:

Typo - I meant is shows up as "Object" when using restangular, but shows up correctly as FormData object when using $http

Reply to this email directly or view it on GitHub:
#420 (comment)

Yes! I temporarily commented out lines 918-921 shown below and it's works! I will work around till you push a fix. Thanks for your help.

//                  if (_.isObject(callObj)) {
//                      callObj = stripRestangular(callObj);
//                  }

Thank you. 

Ill leave this open meanwhile


Mobile mail. Excuse brevity and typos.

On Thu, Nov 21, 2013 at 8:44 PM, zohebsait notifications@github.com
wrote:

Yes! I temporarily commented out lines 918-921 shown below and it's works! I will work around till you push a fix. Thanks for your help.

//                  if (_.isObject(callObj)) {
//                      callObj = stripRestangular(callObj);
//                  }

Reply to this email directly or view it on GitHub:
#420 (comment)

was this fixed?

Not yet, please comment those lines temporarily. I haven’t had much time lately to work on this bugs, but I’ll try to work on them ASAP.


Martin Gontovnikas
Software Engineer
Buenos Aires, Argentina

Twitter: @mgonto (https://twitter.com/mgonto)
Linkedin: http://www.linkedin.com/in/mgonto
Github: https://github.com/mgonto

On Wednesday, December 4, 2013 at 9:58 AM, Alonisser wrote:

was this fixed?


Reply to this email directly or view it on GitHub (#420 (comment)).

Pushing fix :)

great!

Twitter:@alonisser https://twitter.com/alonisser
LinkedIn Profile http://www.linkedin.com/in/alonisser
Facebook https://www.facebook.com/alonisser
_Tech blog:_4p-tech.co.il/blog
_Personal Blog:_degeladom.wordpress.com
Tel:972-54-6734469

On Mon, Dec 9, 2013 at 10:49 PM, Martin Gontovnikas <
notifications@github.com> wrote:

Pushing fix :)


Reply to this email directly or view it on GitHubhttps://github.com//issues/420#issuecomment-30171863
.

This thread just saved my bacon, but I wasted some time trying to figure out how to set the headers correctly. Here's a solution that worked for me. It's Coffeescript. Sorry. Not my choice.

    postIt = () ->
      formData = new FormData()
      formData.append('file', file) # file is an ArrayBuffer read with fileReader.readAsArrayBuffer(file)
      formData.append('name', file.name)

      apiAuth.one('api/client', client.id).one('note', note.id).withHttpConfig({transformRequest: angular.identity}).customPOST(formData, 'file', undefined, {'Content-Type': undefined}).then (res)->
        deferred.resolve(res)

It still here, guys. I have same problem with 1.4
What i can do with it?

Restangular.all('points').withHttpConfig({transformRequest: angular.identity}).customPOST(formData,'',undefined,{'Content-Type': undefined}).then(function (response) {

Hi,
All seems to be OK when you don't set global default headers.
When adding the following default values in config step, form data are posted without requested headers : form-data and boundary but with 'application/json'.
RestangularProvider.setDefaultHeaders({
"Content-Type": "application/json",
"X-Requested-With": "XMLHttpRequest"
});

When removing "Content-Type": "application/json" from config , customPOST(formData,'',undefined,{'Content-Type': undefined}) does its stuff.

Any idea?

Many thanks in advance.

Cheers

+1

Hi!

I'm trying to implement this but can't figure it out with my use case since it seems that Restangular.service('entities') doesn't have a valid .withHttpConfig() method.

How can achieve that functionality using the service approach?

Thanks in advance.

It seems that Restangular.service('entities') only exports getList(), get(), post() methods :(

Nevertheless, in my Entities service I changed Restangular.service('entities') for Restangular.all('entities') and the file upload worked like a charm.

I'm having the exact same issue with using httpConfigs for objects created with .service

I have provided a PR, because I think this is a bit of a inconsistency. See #1068

Thanks for comments and code snippets. It helps quite a lot!

I have a working implementation but I'm halfly satified because it uses Base64-encoded data upload which seems less efficient than Multipart Upload, and would not work with heavy file (as far as I understood).

@deltaepsilon I tried your code but it only sends the file itself, whereas I would like to update my user during the same call. It should be possible as that's what a regular form does.

Here's the code:

View:

<img ng-src="{{user.avatar}}" class="img-circle img-responsive">
<input type="file" name="avatar" onchange="angular.element(this).scope().updateClientImage(this.files)" />
<input class="btn btn-success btn-lg" type="submit" value="Save" ng-click="save_user()" />

Controller:

        $scope.save_user = function() {
            var deferred;
            // if ($scope.file) {
            //     var formData = new FormData();
            //     formData.append('user[avatar]', $scope.file);
            //     deferred = $scope.user.withHttpConfig({transformRequest: angular.identity}).customPUT(formData, undefined, undefined, {'Content-Type': undefined});
            // } else {
                deferred = $scope.user.put();
            // }
            deferred.then(function(res) {
                console.log(res.msg);
            }, function(err){
                console.log('An error occured:');
                console.log(err);
            });
        };

        $scope.updateClientImage = function(files) {
            $scope.file = files[0];
            var reader = new FileReader();
            reader.onload = function(e) {
                $scope.user.avatar = e.target.result;
                $scope.$apply();
            };
            reader.readAsDataURL($scope.file);
        };

Simply calling $scope.user.put() sends a Base64-encoded image as a user.avatar attributes, which works Ok in my backend.

But how can I have a customPUT that sends over the user attributes + the formData - user.avatar (otherwise it is sent twice)?

 $scope.user.withHttpConfig({transformRequest: angular.identity}).customPUT(formData, undefined, undefined, {'Content-Type': undefined});

I'll report final working solution. Thanks a lot!

Any progress on this? I'm having the same problem. :(

Waxo commented

I'm using jsPDF to create a PDF then i try to submit it with return Restangular.all('users').one('pdf', id).withHttpConfig({transformRequest: angular.identity}).customPOST(fd, 'submit', undefined, {'Content-Type': undefined});
But the header is application/json and i need a multipart/*
If i force the Content-Type to multipart/form-data i have a boundary problem. Is there a workaround ?

@ncourtial, the default headers problem is fixed like so:
postFile: function (dataUri, path, keyName, fileName) {
var authenticatedHeaders = angular.extend({},
DEFAULT_HTTP_HEADERS,
{"x-authtoken": localStorage.authtoken});
authenticatedHeaders["Content-Type"] = undefined;
var formData = new FormData();
var blob = dataURItoBlob(dataUri);
formData.append(keyName, blob, fileName);
// kill default headers
var restangularRoute = Restangular.withConfig(function (RestangularConfigurer) {
RestangularConfigurer.setDefaultHeaders(authenticatedHeaders);
});
return restangularRoute.one(path).withHttpConfig({transformRequest: angular.identity})
.customPOST(formData, '', undefined, authenticatedHeaders);
}

if i set ["Content-Type"] = undefined, It calls webservice , but i i want to send text data in other language (utf-8) it won't receive as expected in Rest api

I found a workaround for setting default headers. Instead of setting undefined, set a function returning undefined:

RestangularProvider.setDefaultHeaders({
  'Content-Type': 'application/json'
});


return API.all(UPLOAD_RESOURCE)
  .withHttpConfig({transformRequest: angular.identity})
  .customPOST(file, 'image', {}, {
    'Content-Type': () => {
      return undefined;
    }
  });

I can't seem to have it work.. this is what I'm using:

I'm using restangular 1.51 with angular 1.6. My html looks like this

<input type="file" name="file" />

and the angular code:

let directive = {
  ..
  link: (scope, element, attrs) => {
        let inputElement = angular.element(element[0].querySelector('input'));
        inputElement.bind('change', function () {
        var formData = new FormData();
        formData.append('file', inputElement[0].files[0]);

        API.all('stores/csv').withHttpConfig({transformRequest: angular.identity})             .customPOST(formData,'' , undefined,
          { 'Content-Type': undefined }).then((response) => {console.log(response);
          });
    });

laravel code:

public function upload(Request $request)
{

    $this->validate($request, [
        'file' => 'required',
        'type'  => 'in:csv,xls,xlsx',
        ]);

    $file = $request->input('file');
    var_dump($file);
    return response()->success(['file' => $file]);
}

thing is the $file here is appearing as an empty array in the laravel dump. The documentation is pretty bad on this. Ideas?

so basically i had to brute force the content-type to application/x-www-form-urlencoded..

btw i'm using restangular 1.51.. more details here https://stackoverflow.com/questions/49077674/how-to-upload-files-with-angular-using-cors/49081727#49081727