I have an API endpoint to which I must send a multipart HTTP request, composed of two parts, file (a file system file) and data (a JSON object).
After some research I found out how to do a multipart request in AngularJS:
$http({
method: 'POST',
url: url,
headers: {
'Content-Type': 'multipart/form-data'
},
data: {
data: model,
file: file
},
transformRequest: customFormDataObject
});
1) The customFormDataObject function initially had this form:
customFormDataObject formDataObject = function (data, headersGetter) {
var fd = new FormData();
angular.forEach(data, function (value, key) {
fd.append(key, value);
});
var headers = headersGetter();
delete headers['Content-Type'];
return fd;
};
The outcome of this implementation is that the individual parts of the request do not have a contentType set to them.
2) After reading some more (https://stackoverflow.com/a/24535293/689216) I tried using a Blob for this, the customFormData object looking like this (a bit of a mess, basically the first part will be of contentType application/json, the second one image/png):
customFormDataObject = function (data, headersGetter) {
var fd = new FormData();
var contentType = 'application/json';
angular.forEach(data, function (value, key) {
fd.append(key, new Blob([JSON.stringify(value)], {
type: contentType
}));
contentType = 'image/png';
});
var headers = headersGetter();
delete headers['Content-Type'];
return fd;
};
This second approach sets the correct contentType for each part of the request, but it does not set any values for the parts.
Basically what happens is with 1) the correct values are set in the multiparts, but the contentType's are not set. With 2) the contentType's are set, but not the values for the multiparts.
Am I missing something? Is this functionality not supposed to work like this?
Thanks!
The easiest way to upload files in Angular:
var fd = new FormData();
fd.append('file', file);
fd.append('data', 'string');
$http.post(uploadUrl, fd, {
transformRequest: angular.identity,
headers: {'Content-Type': undefined}
})
.success(function(){
})
.error(function(){
});
Absolutely essential are the following two properties of the config object:
transformRequest: angular.identity
overrides Angular's default serialization, leaving our data intact.
headers: {'Content-Type': undefined }
lets the browser detect the correct Content-Type as multipart/form-data, and fill in the correct boundary.
Nothing else worked for me! Courtesy of Lady Louthan's wonderful blogpost.
Have you tried something like this:
$httpProvider.defaults.transformRequest = function(data) {
if (data === undefined){
return data;
}
var formData = new FormData();
for (var key in data){
if(typeof data[key] == 'object' && !(data[key] instanceof File)){
formData.append(key, JSON.stringify(data[key]));
}else{
formData.append(key, data[key]);
}
}
return formData;
};
I just solve exactly the same problem.
After some tests, I had this situation that you've described:
Basically what happens is with 1) the correct values are set in the multiparts, but the contentType's are not set. With 2) the contentType's are set, but not the values for the multiparts.
To fix this problem, I had to use Blob and Post Ajax instead of $http Post.
It seems that $http does not work correctly in this case.
Code:
var formData = new FormData();
var blobId = new Blob([100], { 'type':'text/plain' });
formData.append('testId', blobId);
var blobImage = fileService.base64ToBlob(contentToDecode, 'image/jpeg');
formData.append('file', blobImage, 'imagem' + (i + 1) + '.jpg');
Request:
$.ajax({
url: url,
data: formData,
cache: false,
contentType: false,
processData: false,
type: 'POST',
success: function(response) {
deferred.resolve(response);
$rootScope.requestInProgress = false;
},
error: function(error) {
deferred.reject(error);
$rootScope.requestInProgress = false;
}
});
You can use https://github.com/danialfarid/ng-file-upload/.
In this file uploader, there is a provision for sending the file and data (in JSON format) separately as you mentioned in your question above.
For Ex:-
var upload = $upload.upload({
url: url,
file: file,
method: 'POST' or 'PUT', default POST,
headers: {'Content-Type': 'multipart/form-data'}, // only for html5
fileName: 'doc.jpg',
fileFormDataName: 'myFile',
data: {'data': model}
});
In the above code, you can send either a POST or PUT request with 'multipart/form-data', file and data object as JSON separately.
For more information you can visit the above link and look at the ReadMe.md of the plug-in.
I know that my approach is a bit different from the one that you are currently following, but the objective is same.
what i did to solve this was.
var formData = new FormData(document.getElementById('myFormId'));
then in my service
var deferred = $q.defer();
$http.post('myurl', formData, {
cache: false,
contentType: false,
processData: false,
})
.success(function (response) {
deferred.resolve(response);
})
.error(function (reject) {
deferred.reject(reject);
});
return deferred.promise;
Related
unable to send file with angular post call
I am trying to post .mp4 file with some data through ionic 1 with angular 1. While posting through POSTMAN it is fine and working. I am getting Success = false in my application.
in POSTMAN, no headers and data is bellow,
Service url with POST request http://services.example.com/upload.php
body in form data
j_id = 4124, type = text
q_id = 6, type = text
u_id = 159931, type = text
file = demo.mp4, type = file
in my app:
$rootScope.uploadQuestion = function () {
var form = new FormData();
form.append("j_id", "4124");
form.append("q_id", "6");
form.append("u_id", "159931");
form.append("file", $rootScope.videoAns.name); //this returns media object which contain all details of recorded video
return $http({
method: 'POST',
headers: { 'Content-Type': 'multipart/form-data' }, // also tried with application/x-www-form-urlencoded
url: 'http://services.example.com/upload.php',
// url: 'http://services.example.com/upload.php?j_id=4124&q_id=8&u_id=159931&file='+$rootScope.videoAns.fullPath,
// data: "j_id=" + encodeURIComponent(4124) + "&q_id=" + encodeURIComponent(8) + "&u_id=" + encodeURIComponent(159931) +"&file=" + encodeURIComponent($rootScope.videoAns),
data: form,
cache: false,
timeout: 300000
}).success(function (data, status, headers, config) {
if (status == '200') {
if (data.success == "true") {
alert('uploading...');
}
}
}).error(function (data, status, headers, config) {
});
}
RECOMMENDED: POST Binary Files Directly
Posting binary files with multi-part/form-data is inefficient as the base64 encoding adds an extra 33% overhead. If the server API accepts POSTs with binary data, post the file directly:
function upload(url, file) {
if (file.constructor.name != "File") {
throw new Error("Not a file");
}
var config = {
headers: {'Content-Type': undefined},
transformRequest: []
};
return $http.post(url, file, config)
.then(function (response) {
console.log("success!");
return response;
}).catch(function (errorResponse) {
console.error("error!");
throw errorResponse;
});
}
Normally the $http service encodes JavaScript objects as JSON strings. Use transformRequest: [] to override the default transformation.
DEMO of Direct POST
angular.module("app",[])
.directive("selectNgFiles", function() {
return {
require: "ngModel",
link: postLink
};
function postLink(scope, elem, attrs, ngModel) {
elem.on("change", function(event) {
ngModel.$setViewValue(elem[0].files);
});
}
})
.controller("ctrl", function($scope, $http) {
var url = "//httpbin.org/post";
var config = {
headers: { 'Content-type': undefined }
};
$scope.upload = function(files) {
var promise = $http.post(url,files[0],config);
promise.then(function(response){
$scope.result="Success "+response.status;
}).catch(function(errorResponse) {
$scope.result="Error "+errorRespone.status;
});
};
})
<script src="//unpkg.com/angular/angular.js"></script>
<body ng-app="app" ng-controller="ctrl">
<input type="file" select-ng-files ng-model="files">
<br>
<button ng-disabled="!files" ng-click="upload(files)">
Upload file
</button>
<pre>
Name={{files[0].name}}
Type={{files[0].type}}
RESULT={{result}}
</pre>
</body>
Posting with 'Content-Type': 'multipart/form-data'
When posting data using the FormData API, it is important to set the content type to undefined:
function uploadQuestion(file) {
var form = new FormData();
form.append("j_id", "4124");
form.append("q_id", "6");
form.append("u_id", "159931");
form.append("file", file); //this returns media object which contain all details of recorded video
return $http({
method: 'POST',
headers: { 'Content-Type': undefined ̶'̶m̶u̶l̶t̶i̶p̶a̶r̶t̶/̶f̶o̶r̶m̶-̶d̶a̶t̶a̶'̶ }, // also tried with application/x-www-form-urlencoded
url: 'http://services.example.com/upload.php',
data: form,
̶c̶a̶c̶h̶e̶:̶ ̶f̶a̶l̶s̶e̶,̶
timeout: 300000
̶}̶)̶.̶s̶u̶c̶c̶e̶s̶s̶(̶f̶u̶n̶c̶t̶i̶o̶n̶ ̶(̶d̶a̶t̶a̶,̶ ̶s̶t̶a̶t̶u̶s̶,̶ ̶h̶e̶a̶d̶e̶r̶s̶,̶ ̶c̶o̶n̶f̶i̶g̶)̶ ̶{̶
}).then(function(response) {
var data = response.data;
var status = response.status;
if (status == '200') {
console.log("Success");
}
̶}̶)̶.̶e̶r̶r̶o̶r̶(̶f̶u̶n̶c̶t̶i̶o̶n̶ ̶(̶d̶a̶t̶a̶,̶ ̶s̶t̶a̶t̶u̶s̶,̶ ̶h̶e̶a̶d̶e̶r̶s̶,̶ ̶c̶o̶n̶f̶i̶g̶)̶ ̶{̶
}).catch(function(response) {
console.log("ERROR");
//IMPORTANT
throw response;
});
}
When the XHR API send method sends a FormData Object, it automatically sets the content type header with the appropriate boundary. When the $http service overrides the content type, the server will get a content type header without the proper boundary.
I have the following ajax code that makes a POST request for a blob to the server,and prints the returned data.
function upload(blob){
var formData = new FormData();
formData.append('file', blob);
$.ajax({
url: "http://custom-url/record.php",
type: 'POST',
data: formData,
contentType: false,
processData: false,
success: function(data) {
console.log(data);
}
});
}
How can I do the same thing in AngularJS?
Instead of appending the blob to FormData, it is more efficient to send the blob directly as the base64 encoding of the FormData API has an extra 33% overhead.
var config = { headers: { 'Content-Type': undefined } };
$http.post(url, blob, config)
.then(function (response) {
var data = response.data;
var status = response.status;
var statusText = response.statusText;
var headers = response.headers;
var config = response.config;
console.log("Success", status);
return response;
}).catch(function (errorResponse) {
console.log("Error", errorResponse.status);
throw errorResponse;
});
It as important to set the content type header to undefined so that the XHR send method can set the content type header. Otherwise the AngularJS framework will override with content type application/json.
For more information, see AngularJS $http Service API Reference.
I want to send itemId in http request. how to do it and what will I see on server req?
here is request:
var fd = new FormData();
var itemId = scope.vm.item._id;
fd.append('file', scope.files[0]);
$http.post('http://localhost:8090/file-upload', fd, {
transformRequest: angular.identity,
headers: {
'Content-Type': undefined
}
});
server
app.post('/file-upload', function(req, res, next) {
console.log("received file");
var pathFile = ' ';
var wId = "itemId";
var storage = multer.diskStorage({
destination: function (req, file, callback) {
callback(null, './uploads/attachments');
pathFile = file.originalname;
},
filename: function (req, file,itemId, cb) {
cb(null, file.originalname);
console.log(itemId);
}
});
var upload = multer({ storage : storage }).single('file');
upload(req,res,function(err) {
if(err) {
return res.end("Error uploading file.");
}
//save file path in work items doc.attachments
var path = './uploads/attachments/'+pathFile;
res.end("File is uploaded");
});
serve code added. How to get itemId on server side?
Theres a couple a ways you could do it, you could just set the params property in your post options and pass in an object with id as a property.
var fd = new FormData();
var itemId = scope.vm.item._id;
fd.append('file', scope.files[0]);
$http.post('http://localhost:8090/file-upload', fd, {
transformRequest: angular.identity,
headers: {'Content-Type': undefined},
params: {id: itemId}
});
You can append JSON string to the FromData object like below.
fd.append('params', JSON.stringify({itemId: itemId}));
On the server side, you will get JSON string in params key.
simple add your parameters in params and pass your all the parameters.
$http.post('http://localhost:8090/file-upload', fd, {
transformRequest: angular.identity,
headers: {'Content-Type': undefined},
params: {id: scope.vm.item._id}
});
hope this works for you.
I want to post form data using $resource, before I was using $http as following :
upload : function(file){
let fd = new FormData();
fd.append('file', file);
$http.post('http://localhost:8080/fileUpload', fd, {
transformRequest: angular.identity,
headers: {'Content-Type': undefined}
})
.success(function(){
})
.error(function(){
});
}
and now I want to use $resource instead, and this is what I tried to do but it didn't work :
upload : function(file){
let fd = new FormData();
fd.append('file', file);
$resource('http://localhost:8080/fileUpload/:id',fd, {
create: {
method: "POST",
transformRequest: angular.identity,
headers: {'Content-Type': undefined}
}
});
}
Edit (Solution) :
From this post AngularJS: Upload files using $resource (solution) there was two solutions the first one which was pointed out by #juandemarco was to configure the $http service but this will transform each and every request made by AngularJS, so the second one which was pointed out by #timetowonder was a better solution since I need to define this behavior only for those $resources that actually need it, so I tried as following :
in my controller :
var fd = new FormData();
fd.append('identityDocUpload', $scope.identityDocUpload);
fileUploadService.create({}, fd).$promise.then(function (res) {
console.log('success');
}).catch(function (err) {
console.log('err');
});
my service :
app
.factory('fileUploadService', function ($resource) {
return $resource('http://localhost:8080/fileUpload/:id', { id: "#id" }, {
create: {
method: "POST",
transformRequest: angular.identity,
headers: { 'Content-Type': undefined }
}
});
});
As pointed out here, you CAN do it the way explained, but you'll have some browser version limitation.
In the case below, he's uploading an image.
AngularJS: Upload files using $resource (solution)
I'm trying to make a post call with a simple object that has this structure:
{"name": "file.txt", "file": file}
file is an object I get from an input file.
I have tried to make this call but i can't submit my object:
var elements = $element[0];
var file = elements.getElementsByTagName('input')[0].files[0];
this.fileName = file.name;
var formData = new FormData();
formData.append('file', file);
var url = 'http://localhost:8080/upload';
var config = {
transformRequest: angular.identity,
headers: {'Content-Type': undefined}
};
$http.post(url, formData, config)
.success(function(data){
$log.info(data);
})
.error(function(err){
$log.error(err);
});
Any ideas on why this isn't working?
I recently had to do something smilar, it took a lot of fenagling to get the request to go through, but this configuration finally worked for us.
sendObj.append("file", fileObj.file, fileObj.file.name);
$http({
headers: {'Content-Type': undefined},
processData:false,
method: 'POST',
cache: false,
url: sendUrl,
data: sendObj,
transformRequest: function(data, headersGetterFunction) {
return data; // do nothing! FormData is very good!
}
})
On a side note, it also took a lot of messing about on the server side so depending on what you have, you may need to do something else. This was for our Spring server.