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

multipart form data submit with restangular #420

Closed
zoheb opened this issue Nov 21, 2013 · 27 comments
Closed

multipart form data submit with restangular #420

zoheb opened this issue Nov 21, 2013 · 27 comments

Comments

@zoheb
Copy link

zoheb commented Nov 21, 2013

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

@mgonto
Copy link
Owner

mgonto commented Nov 21, 2013

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

@mgonto mgonto closed this as completed Nov 21, 2013
@zoheb
Copy link
Author

zoheb commented Nov 21, 2013

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.

@mgonto mgonto reopened this Nov 21, 2013
@mgonto
Copy link
Owner

mgonto commented Nov 21, 2013

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.

@zoheb
Copy link
Author

zoheb commented Nov 21, 2013

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.

@zoheb
Copy link
Author

zoheb commented Nov 21, 2013

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

@mgonto
Copy link
Owner

mgonto commented Nov 21, 2013

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 [email protected]
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)

@zoheb
Copy link
Author

zoheb commented Nov 21, 2013

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);
//                  }

@mgonto
Copy link
Owner

mgonto commented Nov 21, 2013

Thank you. 

Ill leave this open meanwhile


Mobile mail. Excuse brevity and typos.

On Thu, Nov 21, 2013 at 8:44 PM, zohebsait [email protected]
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)

@alonisser
Copy link

was this fixed?

@mgonto
Copy link
Owner

mgonto commented Dec 4, 2013

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)).

@mgonto mgonto closed this as completed in f2bca15 Dec 9, 2013
@mgonto
Copy link
Owner

mgonto commented Dec 9, 2013

Pushing fix :)

@alonisser
Copy link

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 <
[email protected]> wrote:

Pushing fix :)


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

@deltaepsilon
Copy link

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)

@gen4sp
Copy link

gen4sp commented Jan 18, 2015

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) {

@ncourtial
Copy link

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

@Tallyb
Copy link

Tallyb commented Jan 24, 2015

+1

@diosney
Copy link

diosney commented Feb 4, 2015

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.

@diosney
Copy link

diosney commented Feb 6, 2015

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.

@steinerj
Copy link

steinerj commented Mar 2, 2015

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

@augnustin
Copy link

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!

@ArthurianX
Copy link

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

@Waxo
Copy link

Waxo commented Jun 19, 2015

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 ?

@rhodul
Copy link

rhodul commented Oct 16, 2015

@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);
}

@suvarnajayanth
Copy link

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

@andrzej-aa
Copy link

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;
    }
  });

@abbood
Copy link

abbood commented Mar 2, 2018

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?

@abbood
Copy link

abbood commented Mar 3, 2018

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

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

No branches or pull requests