mgonto / restangular

AngularJS service to handle Rest API Restful Resources properly and easily

Geek Repo:Geek Repo

Github PK Tool:Github PK Tool

multipart form data submit with restangular

zoheb opened this issue · 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

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. :(

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