AngularJS: how to implement a simple file upload with multipart form? - angularjs

I want to do a simple multipart form post from AngularJS to a node.js server,
the form should contain a JSON object in one part and an image in the other part,
(I'm currently posting only the JSON object with $resource)
I figured I should start with input type="file", but then found out that AngularJS can't bind to that..
all the examples I can find are for wraping jQuery plugins for drag & drop. I want a simple upload of one file.
I'm new to AngularJS and don't feel comfortable at all with writing my own directives.

A real working solution with no other dependencies than angularjs (tested with v.1.0.6)
html
<input type="file" name="file" onchange="angular.element(this).scope().uploadFile(this.files)"/>
Angularjs (1.0.6) not support ng-model on "input-file" tags so you have to do it in a "native-way" that pass the all (eventually) selected files from the user.
controller
$scope.uploadFile = function(files) {
var fd = new FormData();
//Take the first selected file
fd.append("file", files[0]);
$http.post(uploadUrl, fd, {
withCredentials: true,
headers: {'Content-Type': undefined },
transformRequest: angular.identity
}).success( ...all right!... ).error( ..damn!... );
};
The cool part is the undefined content-type and the transformRequest: angular.identity that give at the $http the ability to choose the right "content-type" and manage the boundary needed when handling multipart data.

You can use the simple/lightweight ng-file-upload directive.
It supports drag&drop, file progress and file upload for non-HTML5 browsers with FileAPI flash shim
<div ng-controller="MyCtrl">
<input type="file" ngf-select="onFileSelect($files)" multiple>
</div>
JS:
//inject angular file upload directive.
angular.module('myApp', ['ngFileUpload']);
var MyCtrl = [ '$scope', 'Upload', function($scope, Upload) {
$scope.onFileSelect = function($files) {
Upload.upload({
url: 'my/upload/url',
file: $files,
}).progress(function(e) {
}).then(function(data, status, headers, config) {
// file is uploaded successfully
console.log(data);
});
}];

It is more efficient to send a file directly.
The base64 encoding of Content-Type: multipart/form-data adds an extra 33% overhead. If the server supports it, it is more efficient to send the files directly:
$scope.upload = function(url, file) {
var config = { headers: { 'Content-Type': undefined },
transformResponse: angular.identity
};
return $http.post(url, file, config);
};
When sending a POST with a File object, it is important to set 'Content-Type': undefined. The XHR send method will then detect the File object and automatically set the content type.
To send multiple files, see Doing Multiple $http.post Requests Directly from a FileList
I figured I should start with input type="file", but then found out that AngularJS can't bind to that..
The <input type=file> element does not by default work with the ng-model directive. It needs a custom directive:
Working Demo of "select-ng-files" Directive that Works with ng-model1
angular.module("app",[]);
angular.module("app").directive("selectNgFiles", function() {
return {
require: "ngModel",
link: function postLink(scope,elem,attrs,ngModel) {
elem.on("change", function(e) {
var files = elem[0].files;
ngModel.$setViewValue(files);
})
}
}
});
<script src="//unpkg.com/angular/angular.js"></script>
<body ng-app="app">
<h1>AngularJS Input `type=file` Demo</h1>
<input type="file" select-ng-files ng-model="fileArray" multiple>
<h2>Files</h2>
<div ng-repeat="file in fileArray">
{{file.name}}
</div>
</body>
$http.post with content type multipart/form-data
If one must send multipart/form-data:
<form role="form" enctype="multipart/form-data" name="myForm">
<input type="text" ng-model="fdata.UserName">
<input type="text" ng-model="fdata.FirstName">
<input type="file" select-ng-files ng-model="filesArray" multiple>
<button type="submit" ng-click="upload()">save</button>
</form>
$scope.upload = function() {
var fd = new FormData();
fd.append("data", angular.toJson($scope.fdata));
for (i=0; i<$scope.filesArray.length; i++) {
fd.append("file"+i, $scope.filesArray[i]);
};
var config = { headers: {'Content-Type': undefined},
transformRequest: angular.identity
}
return $http.post(url, fd, config);
};
When sending a POST with the FormData API, it is important to set 'Content-Type': undefined. The XHR send method will then detect the FormData object and automatically set the content type header to multipart/form-data with the proper boundary.

I just had this issue. So there are a few approaches. The first is that new browsers support the
var formData = new FormData();
Follow this link to a blog with info about how support is limited to modern browsers but otherwise it totally solves this issue.
Otherwise you can post the form to an iframe using the target attribute.
When you post the form be sure to set the target to an iframe with its display property set to none.
The target is the name of the iframe. (Just so you know.)
I hope this helps

You could upload via $resource by assigning data to params attribute of resource actions like so:
$scope.uploadFile = function(files) {
var fdata = new FormData();
fdata.append("file", files[0]);
$resource('api/post/:id', { id: "#id" }, {
postWithFile: {
method: "POST",
data: fdata,
transformRequest: angular.identity,
headers: { 'Content-Type': undefined }
}
}).postWithFile(fdata).$promise.then(function(response){
//successful
},function(error){
//error
});
};

I know this is a late entry but I have created a simple upload directive. Which you can get working in no time!
<input type="file" multiple ng-simple-upload web-api-url="/api/post"
callback-fn="myCallback" />
ng-simple-upload more on Github with an example using Web API.

I just wrote a simple directive (from existing one ofcourse) for a simple uploader in AngularJs.
(The exact jQuery uploader plugin is https://github.com/blueimp/jQuery-File-Upload)
A Simple Uploader using AngularJs (with CORS Implementation)
(Though the server side is for PHP, you can simple change it node also)

Related

Uploading file in API makes error 400

I'm working on an API project which communicate with another API, my front is AngularJS
I try to upload a file (a picture) an post it through an API which already works for other posts (other posts are not uploads, just data)
I can upload the picture and treat it in a formdata, but when I try to post it, I have a 400 error with no 'Access-Control-Allow-Origin'. But my others post methods work well but this one makes trouble.
My Ajax post method:
apiDataService.postPicture = function(formData, callback) {
$http({
method: 'POST',
url: apiUrl + '/pictures',
data: formData,
contentType: false,
processData: false,
headers: {
'Authorization': localStorageService.get('auth')
}
}).then(function successCallback(response) {
// retrieve response data
callback(response.data);
}, function errorCallback(response) {
// an error occured
});
};
I took the pattern of my others post methods so, it would works...
My uploading function:
self.addPicture = (image) => {
var fileInput = document.getElementById('the-file');
var file = fileInput.files[0];
var formData = new FormData();
formData.append('file', file);
apiDataService.postPicture(formData);
}
And my view:
<form id="form-id">
<div class="col-xs-12">
<span>Image :</span>
<br/>
<input id="the-file" type="file" placeholder="Image du Procédé" ng-model="picture.file" class="form-control" />
</div>
<div class="col-xs-12">
<button ng-click="addPicture(image)">Enregistrer image</button>
</div>
</form>
If you have any ideas, I don't understand my error...
It is certainly due to your CORS options your server are using. Maybe you don't allow data header or something like that.
#Nounnoune can you try to add below header I had Same Problem so I added below header and work like charm .
'Content-type': 'application/x-www-form-urlencoded; charset=utf-8'
There are already many Angular.js modules to perform file uploading.
These two have explicit support for older browsers:
Uses iframes as a fallback
Uses FileAPI/Flash as a fallback
I finally got a good form, a basic one only in HTML, it's not as pretty as I would like it to be, but it works! Just have to optimize it.

Handling file submit API response in angular

I have a form where a user uploads a file to my node server, does some stuff, and sends a JSON response.
I do not make the POST through the control, its via submitting a form. After my node code does some stuff, it sends this response succesfully.
res.json({
results: "TRUE",
file: rows,
column: pIndex,
rowCount: rows.length
})
Problem, i need to access this json response in my angular app. After a user submits form, they see raw json of this response and the app redirects to my endpoint: http://localhost:8000/upload-file
What do i do to access this response in my angular app without uploading file via controller($http.post)
I have no idea, im much new to javascript. Thanks!
Upload File with AngularJS
The template
<input type=file files-input ng-model="files" /><br>
<button ng-disabled="!files[0]" ng-click="upload()">Upload</button><br>
The Upload button becomes active after the file is selected.
The files-input Directive
app.directive("filesInput", function() {
return {
require: "ngModel",
link: function linkFn (scope, elem, attrs, ngModel) {
elem.on("change", function (e) {
ngModel.$setViewValue(elem[0].files);
});
}
};
});
The directive uses the ngModelController API to bind the selected files to a scope variable.
The upload() function
var vm = $scope;
var url = "API URL";
var config = { headers: {"Content-Type": undefined} };
vm.upload = function() {
//USE formData for Content-Type multipart/formdata
//var formData = new $window.FormData();
//formData.append("file-0", vm.files[0]);
//USE files[0] for binary upload
$http.post(url, vm.files[0], config)
.then(function(response) {
vm.result = "SUCCESS";
vm.data = response.data.data;
}).catch(function(response) {
vm.result = "ERROR "+response.status;
});
};
It is important to set Content-Type: undefined so that the AngularJS framework doesn't set the content type to application/json. The XHR send method will then set the content type according the type of the given object.
It is more efficient to send the file directly, but if content type multipart/formdata with base64 encoding is desired, use the formData API to create a formData object for the XHR API to send.
The DEMO on PLNKR.
This is because you aren't using the Angular controller. A regular HTML form, by default will attempt to make a URL params encoded POST request to the URL defined in the action="" attribute, or, if not defined, it will POST to the current URL. The result of the post is then simply spit out into the browser window.
Angular has a directive ngSubmit that is for intercepting this default behavior so that you can handle it in a controller:
<form ng-submit="mySubmitHandler()">
...
<input type="text" ng-model="formData.myField"/>
</form>
In your Angular controller you can now do whatever you wish. Typically you make a POST request using the $http provider:
function myController($scope) {
$scope.formData = {
myField: '',
};
$scope.mySubmitHandler = function () {
$http.post('/someUrl', formData).then(function(response) {
//handle the response from Node.js
});
};
}
The problem with this is that you are trying to upload a file. Trying to upload files with $http can be daunting. Your best bet is to use a 3rd party library to facilitate file upload in angular, such as ng-file-upload. It will come with it's own set of instructions that will allow you to handle the Node response inside of Angular.

Upload file without using Formdata in AngularJs

Is there any way to upload files without using formdata in Angular Js. I have checked the below links, which have not helped:
https://stackoverflow.com/questions/32432299/upload-file-using-jquery-without-using-html5
How to send/ Upload file without using FORMDATA in jquery ajax
How can I upload file in JavaScript and PHP without using FormData
I do not want to use any plug-in, and cannot use iframes. I want to upload single file. I have the service created as below, to which I have to pass the image data.
myAPIService.postUploadImage = function (formdata) {
var request = {
url: urlBase + 'UploadImage',
method: 'POST',
data: formdata,
headers: {
'Content-Type': undefined
}
}
return $http(request);
};
The UI is defined as below:
<input type="file" class="form-control" id="imageUploadfile" name="Imagefile" accept="image/*" />
<input type="button" name="imageUploadButton" ng-click="uploadFiles()" value="Upload" />
Requesting Admin/moderators to not mark it as duplicate, as I have tried most of the links, but that has not worked.
How to upload single file in AngularJS without using plugins,iframe and which works in IE 11, Chrome.
Thanks

How send POST request in angularjs with _csrf?

I want sending POST requests to service under spring security.
if i using form submission (with hidden input) - it's working fine.
But i don't know, how send request with $http.post.
my form:
<form method="POST" type="submit" action="http://localhost:8888/rest/post1" />
<button>345</button>
<input name="${_csrf.parameterName}"
value="${_csrf.token}" />
</form>
it work.
I save params:
<script>
var a = "${_csrf.parameterName}";
var b = "${_csrf.token}";
</script>
My ng-click function:
$http.post('http://localhost:8888/rest/post1', {"_csrf": b}, {
"headers" :
{ "_csrf" : b }
}).success(function(data) {
console.log("ok");
}).error(function(){
console.log("no");
});
It always writing "no".
I think, that need to send variable as webForm, but how do it in $http.post?
Fiddler write: Content-Type is 'application/json'; this inspector supports 'x-www-form-urlencoded' only
Server console:
org.springframework.web.servlet.PageNotFound handleHttpRequestMethodNotSupported
WARNING: Request method 'POST' not supported
Help me, please!
You might need to set "_csrf" in the request header. You are passing it as an argument for a method. This won't work. I am not much aware of angular.js but you must need to pass it as request header or request payload. I found some reference. Here it goes:
$httpProvider.defaults.xsrfCookieName = 'csrftoken';
$httpProvider.defaults.xsrfHeaderName = 'X-CSRFToken';
Hope this helps you to solve your problem. Cheers !!!
I solved this)
var request = $http({
method: "post",
url: "http://localhost:8888/rest/post1",
headers: {'Content-Type': 'application/x-www-form-urlencoded'},
transformRequest: function(){return a+"="+b;},
data: {}
});

Upload multipart form data with filename in Request Payload

I am still confused about different method of uploading files. The backend server is not under my control but I can upload a file using Swagger page or Postman. That means the server is functioning OK. But when I use AngularJS to do the upload, it doesn't work.
Here is what works using Postman to test. I am just using form-data:
Notice that Request Headers has Content-Type as multipart/form-data. But the Request Payload has filename and Content-Type as image/png.
Here is my code:
$http({
method: 'POST',
url: ApiUrlFull + 'Job/Item?smartTermId=0&name=aaa1&quantity=1&ApiKey=ABC',
headers: { 'Content-Type': undefined },
transformRequest: function(data) {
var fd = new FormData();
fd.append('file', params.imageData);
return fd;
}
})
params is just an object with file url in imageData.
My code also send similar URL params (so we can ignore that causing issues). But the Request Payload is base64 and it looks different as it is missing the filename field.
I have zero control of the backend and it is written in .NET.
So I guess my question is: Using Angular (either $http or $resource), how do I modify the request so that I am sending the correct Request Payload as how Postman does it? I cannot figure out how to reverse engineer this.
I have tried this https://github.com/danialfarid/ng-file-upload and it actually did OPTIONS request first before POST (assuming CORS issue). But the server gave 405 error for OPTIONS.
You can use something along the line of:
<input type="file" name="file" onchange="uploadFile(this.files)"/>
And in your code:
$scope.uploadFile = function(files) {
var fd = new FormData();
//Take the first selected file
fd.append("file", files[0]);
var uploadUrl = ApiUrlFull + 'Job/Item?smartTermId=0&name=aaa1&quantity=1&ApiKey=ABC';
$http.post(uploadUrl, fd, {
withCredentials: true,
headers: {'Content-Type': undefined },
transformRequest: angular.identity
}).success( ...all right!... ).error( ..damn!... );
};
My need was a follows.
In the form there is a default picture.
Clicking the picture opens a file select window.
When the user selects a file, it is uploaded right away to the server.
As soon as I get a response that the file is valid display the picture to the user instead of the default picture, and add a remove button next to it.
If the user clicks on an existing picture, the file select window reopens.
I tried to use a few code snippets on github that didn't solve the problem, but guided me in the right way, And what I ended up doing is as so:
Directive
angular.module("App").directive('fileModel', function ($parse) {
return {
restrict: 'A',
link: function (scope, element, attrs) {
scope.files = {};
var model = $parse(attrs.fileModel);
var modelSetter = model.assign;
// I wanted it to upload on select of file, and display to the user.
element.bind('change', function () {
scope.$apply(function () {
modelSetter(scope, element[0].files[0]);
});
// The function in the controller that uploads the file.
scope.uploadFile();
});
}
};
});
Html
<div class="form-group form-md-line-input">
<!-- A remove button after file has been selected -->
<span class="icon-close pull-right"
ng-if="settings.profile_picture"
ng-click="settings.profile_picture = null"></span>
<!-- Show the picture on the scope or a default picture -->
<label for="file-pic">
<img ng-src="{{ settings.profile_picture || DefaultPic }}"
class="clickable" width="100%">
</label>
<!-- The actual form field for the file -->
<input id="file-pic" type="file" file-model="files.pic" style="display: none;" />
</div>
Controller
$scope.DefaultPic = '/default.png';
$scope.uploadFile = function (event) {
var filename = 'myPic';
var file = $scope.files.pic;
var uploadUrl = "/fileUpload";
file('upfile.php', file, filename).then(function (newfile) {
$scope.settings.profile_picture = newfile.Results;
$scope.files = {};
});
};
function file(q, file, fileName) {
var fd = new FormData();
fd.append('fileToUpload', file);
fd.append('fn', fileName);
fd.append('submit', 'ok');
return $http.post(serviceBase + q, fd, {
transformRequest: angular.identity,
headers: { 'Content-Type': undefined }
}).then(function (results) {
return results.data;
});
}
Hope it helps.
P.S. A lot of code was striped from this example, if you need clarification just comment.

Resources