Reputation: 58742
I have the below request in python
import requests, json, io
cookie = {}
payload = {"Name":"abc"}
url = "/test"
file = "out/test.json"
fi = {'file': ('file', open(file) )}
r = requests.post("http://192.168.1.1:8080" + url, data=payload, files=fi, cookies=cookie)
print(r.text)
which send a file, and form fields to the backend. How can I do the same (sending file + form fields) with Angular $http. Currently, I do like this, but not sure how to send the file too.
var payload = {"Name":"abc"};
$http.post('/test', payload)
.success(function (res) {
//success
});
Upvotes: 51
Views: 140911
Reputation: 6399
I was unable to get Pavel's answer working as in when posting to a Web.Api application.
The issue appears to be with the deleting of the headers.
headersGetter();
delete headers['Content-Type'];
In order to ensure the browsers was allowed to default the Content-Type along with the boundary parameter, I needed to set the Content-Type to undefined. Using Pavel's example the boundary was never being set resulting in a 400 HTTP exception.
The key was to remove the code deleting the headers shown above and to set the headers content type to null manually. Thus allowing the browser to set the properties.
headers: {'Content-Type': undefined}
Here is a full example.
$scope.Submit = form => {
$http({
method: 'POST',
url: 'api/FileTest',
headers: {'Content-Type': undefined},
data: {
FullName: $scope.FullName,
Email: $scope.Email,
File1: $scope.file
},
transformRequest: function (data, headersGetter) {
var formData = new FormData();
angular.forEach(data, function (value, key) {
formData.append(key, value);
});
return formData;
}
})
.success(function (data) {
})
.error(function (data, status) {
});
return false;
}
Upvotes: 26
Reputation: 5300
Please, have a look on my implementation. You can wrap the following function into a service:
function(file, url) {
var fd = new FormData();
fd.append('file', file);
return $http.post(url, fd, {
transformRequest: angular.identity,
headers: { 'Content-Type': undefined }
});
}
Please notice, that file
argument is a Blob
. If you have base64
version of a file - it can be easily changed to Blob
like so:
fetch(base64).then(function(response) {
return response.blob();
}).then(console.info).catch(console.error);
Upvotes: 3
Reputation: 21
here is my solution:
// Controller
$scope.uploadImg = function( files ) {
$scope.data.avatar = files[0];
}
$scope.update = function() {
var formData = new FormData();
formData.append('desc', data.desc);
formData.append('avatar', data.avatar);
SomeService.upload( formData );
}
// Service
upload: function( formData ) {
var deferred = $q.defer();
var url = "/upload" ;
var request = {
"url": url,
"method": "POST",
"data": formData,
"headers": {
'Content-Type' : undefined // important
}
};
console.log(request);
$http(request).success(function(data){
deferred.resolve(data);
}).error(function(error){
deferred.reject(error);
});
return deferred.promise;
}
// backend use express and multer
// a part of the code
var multer = require('multer');
var storage = multer.diskStorage({
destination: function (req, file, cb) {
cb(null, '../public/img')
},
filename: function (req, file, cb) {
cb(null, file.fieldname + '-' + Date.now() + '.jpg');
}
})
var upload = multer({ storage: storage })
app.post('/upload', upload.single('avatar'), function(req, res, next) {
// do something
console.log(req.body);
res.send(req.body);
});
<div>
<input type="file" accept="image/*" onchange="angular.element( this ).scope().uploadImg( this.files )">
<textarea ng-model="data.desc" />
<button type="button" ng-click="update()">Update</button>
</div>
Upvotes: 2
Reputation: 192
In my solution, i have
$scope.uploadVideo = function(){
var uploadUrl = "/api/uploadEvent";
//obj with data, that can be one input or form
file = $scope.video;
var fd = new FormData();
//check file form on being
for (var obj in file) {
if (file[obj] || file[obj] == 0) {
fd.append(obj, file[obj]);
}
}
//open XHR request
var xhr = new XMLHttpRequest();
// $apply to rendering progress bar for any chunking update
xhr.upload.onprogress = function(event) {
$scope.uploadStatus = {
loaded: event.loaded,
total: event.total
};
$scope.$apply();
};
xhr.onload = xhr.onerror = function(e) {
if (this.status == 200 || this.status == 201) {
//sucess
$scope.uploadStatus = {
loaded: 0,
total: 0
};
//this is for my solution
$scope.video = {};
$scope.vm.model.push(JSON.parse(e.currentTarget.response));
$scope.$apply();
} else {
//on else status
}
};
xhr.open("POST", uploadUrl, true);
//token for upload, thit for my solution
xhr.setRequestHeader("Authorization", "JWT " + window.localStorage.token);
//send
xhr.send(fd);
};
}
Upvotes: 0
Reputation: 5136
There are other solutions you can look into http://ngmodules.org/modules/ngUpload as discussed here file uploader integration for angularjs
Upvotes: 0
Reputation: 2102
I had similar problem when had to upload file and send user token info at the same time. transformRequest
along with forming FormData
helped:
$http({
method: 'POST',
url: '/upload-file',
headers: {
'Content-Type': 'multipart/form-data'
},
data: {
email: Utils.getUserInfo().email,
token: Utils.getUserInfo().token,
upload: $scope.file
},
transformRequest: function (data, headersGetter) {
var formData = new FormData();
angular.forEach(data, function (value, key) {
formData.append(key, value);
});
var headers = headersGetter();
delete headers['Content-Type'];
return formData;
}
})
.success(function (data) {
})
.error(function (data, status) {
});
For getting file $scope.file
I used custom directive:
app.directive('file', function () {
return {
scope: {
file: '='
},
link: function (scope, el, attrs) {
el.bind('change', function (event) {
var file = event.target.files[0];
scope.file = file ? file : undefined;
scope.$apply();
});
}
};
});
Html:
<input type="file" file="file" required />
Upvotes: 136
Reputation: 15931
I recently wrote a directive that supports native multiple file uploads. The solution I've created relies on a service to fill the gap you've identified with the $http service. I've also included a directive, which provides an easy API for your angular module to use to post the files and data.
Example usage:
<lvl-file-upload
auto-upload='false'
choose-file-button-text='Choose files'
upload-file-button-text='Upload files'
upload-url='http://localhost:3000/files'
max-files='10'
max-file-size-mb='5'
get-additional-data='getData(files)'
on-done='done(files, data)'
on-progress='progress(percentDone)'
on-error='error(files, type, msg)'/>
You can find the code on github, and the documentation on my blog
It would be up to you to process the files in your web framework, but the solution I've created provides the angular interface to getting the data to your server. The angular code you need to write is to respond to the upload events
angular
.module('app', ['lvl.directives.fileupload'])
.controller('ctl', ['$scope', function($scope) {
$scope.done = function(files,data} { /*do something when the upload completes*/ };
$scope.progress = function(percentDone) { /*do something when progress is reported*/ };
$scope.error = function(file, type, msg) { /*do something if an error occurs*/ };
$scope.getAdditionalData = function() { /* return additional data to be posted to the server*/ };
});
Upvotes: 5
Reputation: 11137
You can also upload using HTML5. You can use this AJAX uploader.
The JS code is basically:
$scope.doPhotoUpload = function () {
// ..
var myUploader = new uploader(document.getElementById('file_upload_element_id'), options);
myUploader.send();
// ..
}
Which reads from an HTML input element
<input id="file_upload_element_id" type="file" onchange="angular.element(this).scope().doPhotoUpload()">
Upvotes: 2