Upload multipart form data with filename in Request Payload - angularjs

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.

Related

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.

Uploading picture with Angular, Express, Mongoose

I'm trying to upload and store picture with Mongoose, Express and Angular. I've picked here the next solution:
.directive('fileModel', ['$parse', function ($parse) {
return {
restrict: 'A',
link: function(scope, element, attrs) {
element.bind('change', function(){
$parse(attrs.fileModel).assign(scope,element[0].files)
scope.$apply();
});
}
};
}])
And the next function in controller:
$scope.uploadFile=function(){
var fd = new FormData();
angular.forEach($scope.files,function(file){
fd.append('file',file);
});
$http.post('http://' + host + ':3000/users/' + $scope.selectedTask._id,fd,
{
transformRequest: angular.identity,
headers: {'Content-Type': undefined}
}).success(function(d){
console.log('yes');
})
}
And html:
<input type = "file" file-model="files" multiple/>
<button ng-click = "uploadFile()">upload me</button>
<li ng-repeat="file in files">{{file.name}}</li>
But for some reason all I'm getting in my endpoint is an empty request object. I'm checking it with the following code in express.js:
user.route('/users/:id')
.post(function (req, res, next) {
console.log(req.body);
})
I think the problem is that I don't know how to store something that is larger then 16MB.
In this example you will see how to store the file you are sending in to your server directory and then pick them up from there and save them. You can also directly save them.
First you pick up the file using angular, if you want you can
check here for more details.
Here is my small example the code is in jade.
input(type="file" name="file" onchange="angular.element(this).scope().selectFile(this.files)")
button(ng-click='savePhoto()') Save
In your angular controller
$scope.savePhoto = function () {
var fd = new FormData();
fd.append("file", $scope.files[0]);
)) ;
$http.post("/xxx/photos", fd, {
withCredentials: true,
headers: { 'Content-Type': undefined },
transformRequest: angular.identity
}).success(function (data) {
$scope.image = data; // If you want to render the image after successfully uploading in your db
});
};
Install multer using npm in your back end. And then in app.js you can set up a middleware to collect the files you are sending in. Just do console.log(req) here to check if you are getting the files till here. Multer does the magic here.
app.use(multer({
dest: path.join(__dirname, 'public/assets/img/profile'),
rename: function (fieldname, filename, req, res) {
console.log(req)// you will see your image url etc.
if(req.session.user) return req.session.user.id;
}
}));
So here the image will be stored in this path (public/assets/img/profile) in your server.
Now you pick up the file from this server and add to your db.
var path = require('path');
var imgPath =path.join(__dirname, '../public/assets/img/profile/' + id + '.jpg'); // this is the path to your server where multer already has stored your image
console.log(imgPath);
var a ;
a = fs.readFileSync(imgPath);
YourSchema.findByIdAndUpdate( id, {
$set:
{'img.data' : a,
'img.contentType' : 'image/png' }
}, function(err, doc) {
if (err)console.log("oi");
}
);
//In case you want to send back the stored image from the db.
yourSchema.findById(id, function (err, doc) {
if (err)console.log(err);
var base64 = doc.img.data.toString('base64');
res.send('data:'+doc.img.contentType+';base64,' + base64);
});
In your schema store the image in type Buffer
img: { data: Buffer}

Render remote PNG image in AngularJS

I am developing an AngularJS application which displays a PNG image retrieved from a server.
If I put the URL (see below) in the browser I can see the image just fine. However, if I want to retrieve such image from my Angular application I cannot manage to display it (although I do receive the data!).
The JS code is the following:
$scope.receivedImage = null;
var url = 'https://subdomain.mydomain.uk/img?latitude=55.57&longitude=-5.16&extent=2000';
$http(
{
method: 'GET',
url: url,
headers: {
Accept: 'image/png'
}
}
).then(
function successCallback(response) {
var data = response.data;
$scope.receivedImage = data;
},
function errorCallback(response) {
console.error(response);
}
);
The problem is that I cannot see the image that is retrieved. To understand better the situation I put in the HTML page the following code:
<div ng-show="receivedImage">
<pre>{{receivedImage}}</pre>
<img data-ng-src="{{receivedImage}}" />
<img data-ng-src="data:image/png;{{receivedImage}}" />
</div>
The '' shows something like
�PNG IHDR�R9�%IDATx��̱ ������ �2��'��j�Z�V��w����LxIEND�B`�
The first '' does not show anything.
The second '' shows an image icon and throws in console an error:
GET
data:image/png;%EF%BF%BDPNG%1A%00%00%00IHDR%00%00%00%1E%00%00%00%1E%08%02%0…%BD%EF%BF%BD%EF%BF%BD%EF%BF%BDL%0E%17x%00%00%00%00IEND%EF%BF%BDB`%EF%BF%BD
net::ERR_INVALID_URL
How can I render this image correctly?
Try setting the ng-src attribute to a variable that is the url.
$scope.url = 'https://subdomain.mydomain.uk/img?latitude=55.57&longitude=-5.16&extent=2000';
and in the markup
<img ng-src="{{url}}" />
If the url is unprotected then the approach from Anthony helps a lot. For my use-case where the URL was protected i had to go with the below approach. In this case i had to inject the authentication headers by overriding angular's http authentication interceptors for accessing the protected URL.
// http call inside a service under a function named getImage()
$http(
{
method: 'GET',
url: 'YOUR_PROTECTED_RESOURCE_URL',
// This is required for getting your data as buffer array
{
responseType: 'arraybuffer'
}
}
).then(
function successCallback(response) {
return response;
},
function errorCallback(response) {
console.error(response);
}
);
Inside your controller or directive the data that comes from the above call has to be handled like so:
// Function to get the image from the server
var handleImage = function(){
MyHttpService.getImage()
.then(function(response){
// Can be used within ng-src fro displaying image
$scope.receivedImage = 'data:image/png;base64,'+_arrayBufferToBase64(response);
}, function(error){
console.error(error);
});
};
// Convert the buffer to base64
var _arrayBufferToBase64 = function( buffer ) {
var binary = '';
var bytes = new Uint8Array( buffer );
var len = bytes.byteLength;
console.log(len);
for (var i = 0; i < len; i++) {
binary += String.fromCharCode( bytes[ i ] );
}
return window.btoa( binary );
};
Hope this helps someone who is trying to access the resource from a protected resource URI.

change Content-type to "application/json" POST method, RESTful API

I am new at AngularJS and I needed your help.
All I need just need is to POST my json to the API and recieve the proper response.
Here's my JSON where i don't know where to code this.
JSON
{
"userId" :"testAgent2",
"token" :"testAgent2",
"terminalInfo":"test2",
"forceLogin" :"false"
}
NOT SURE IF I'm doing this right.
CONTROLLER.JS
function UserLoginCtrl($scope, UserLoginResource) {
//Save a new userLogin
$scope.loginUser = function() {
var loggedin = false;
var uUsername = $scope.userUsername;
var uPassword = $scope.userPassword;
var uforcelogin = 'true';
UserLoginResource.save();
}
}
SERVICES.JS
angular.module('UserLoginModule', ['ngResource'])
.factory('UserLoginResource', function($resource, $http) {
$http.defaults.useXDomain = true;
delete $http.defaults.headers.common['X-Requested-With'];
$http.defaults.headers.post["Content-Type"] = "application/json"; //NOT WORKING
return $resource('http://123.123.123.123\\:1234/SOME/LOCATION/THERE', {}, {
save: {
method:'POST',
headers: [{'Content-Type': 'application/json'}]
} //NOT WORKING EITHER
});
});
INDEX.HTML
<html ng-app>
<head>
<script src="js/lib/angular/angular.js"></script>
<script src="js/lib/angular/angular-resource.js"></script>
</head>
<body ng-controller="UserLoginCtrl">
<form class="form-horizontal" name="form-horizontal" ng-submit="loginUser();">
<div class="button-login">
<!-- start: button-login -->
<button class="btn btn-primary" type="submit">Login</button>
</div>
</form>
</body>
</html>
I kept on getting a response like Unsupported Media Type. I don't know, what else to do.
Assuming you are able to use one of the more recent "unstable" releases, the correct syntax to change the header is.
app.factory('BarService', function ($resource) {
var BarService = $resource('/foo/api/bars/:id', {}, {
'delete': {
method: 'DELETE',
headers: {
'Content-Type': 'application/json'
}
}
});
return BarService;
});
I find the $resource service is a tremendously powerful tool for building applications and has matured to a point that you do not need to fall back to $http as much. Plus its active record like patterns are damn convenient.
Posting a JSON object is quite easy in Angular. All you need to do is the following:
Create a Javascript Object
I'll use your exact properties from your code.
var postObject = new Object();
postObject.userId = "testAgent2";
postObject.token = "testAgent2";
postObject.terminalInfo = "test2";
postObject.forceLogin = "false";
Post the object to the API
To post an object to an API you merely need a simple $http.post function. See below:
$http.post("/path/to/api/", postObject).success(function(data){
//Callback function here.
//"data" is the response from the server.
});
Since JSON is the default method of posting to an API, there's no need to reset that. See this link on $http shortcuts for more information.
With regards to your code specifically, try changing your save method to include this simple post method.
The right way to set 'Content-Type': 'application/json' is setting a transformRequest function for the save action.
angular.module('NoteWrangler')
.factory('NoteNgResource', function NoteNgResourceFactory($resource) {
// https://docs.angularjs.org/api/ngResource/service/$resource
return $resource("./php/notes/:id", {}, {
save : { // redefine save action defaults
method : 'POST',
url : "./php/notes", // I dont want the id in the url
transformRequest: function(data, headers){
console.log(headers);
headers = angular.extend({}, headers, {'Content-Type': 'application/json'});
console.log(headers);
console.log(data);
console.log(angular.toJson(data));
return angular.toJson(data); // this will go in the body request
}
}
});
});
It seems there isn't a method to clear query parameters, the request will have both...

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

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)

Resources