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.
Related
I found a lot of similar discussion about this topic but unfortunately none of them fit my scenario.
I'm trying to mock the backend response from Protractor for testing a new functionality that is not present in the real API at the moment.
I tried different ways for achieving that but with no luck. Every time I run the protractor test, the http request is performed to real API instead of intercepting the request.
Here is my scenario:
I have an AngularJS application and there is a search input box in one view like this:
<input type="text" class="form-control rounded input-lg" placeholder="Search Contacts" ng-model="contactCtrl.filter" ng-change="contactCtrl.inspectFilter()" focus="true" id="inputSearch">
Then, I have a controller for that:
function inspectFilter() {
$q.all([
comService.indexContacts($rootScope.$user.cloudContacts, vm.filter)
.then(function(response) {
vm.contacts = angular.copy(contacts);
})
.catch(function() {
})
]).then(function() {
});
}
}
And the comService.indexContacts that performs the http request:
function indexContacts(url, filter) {
var filtered_url = filter ? url + '?displayName=' + encodeURIComponent(filter) : url;
initializeRequest();
req = {
headers : {
'Authorization' : getAuthenticationToken()
},
method : 'GET',
url : filtered_url
};
return $http(req);
}
I'm not going to explain the logic of everything but let's just say that when the user type something in the input filed, the indexContacts function triggers a GET request to the API and the user can see a list of contacts rendered on the screen.
Now I would very like to intercept that $http(req) in my Protractor test and return a mock JSON back but I don't understand how.
'use strict';
describe('Making a call from the contact detail screen', function() {
beforeAll(function() {
contacts.goToContacts();
element(by.id('inputSearch')).clear().sendKeys('gai');
});
describe('UAT1 - Clicking the number to make a call', function() {
it('Given that the user is on the Cloud contact/user detail screen', function() {
element(by.repeater('contact in contactCtrl.results').row(0)).element(by.css('.tdName')).click();
dom.waitForElementDisplayed(element(by.css('.contact-modal')));
});
...
...
Ok, what I do in here is injecting some text into the search field with: element(by.id('inputSearch')).clear().sendKeys('gai'); and this works, but, I want to intercept the http request triggered by the previous comService and, instead, return a mock JSON back to the application to render a custom list of users instead of using the real API for that.
How can I do that????
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.
Trying to create a contact form and feedback form for my website. Here is my route and controller I'm using however I need to understand what's going on with my routes and how to capture the input fields from the form implement this inside MEAN.JS:
route.js:
app.route('/mail').get(mail.createmail);
app/controller.js:
exports.createmail = function(req, res) {
var mailOpts, smtpTrans;
// create reusable transporter object using SMTP transport
var transporter = nodemailer.createTransport({
service: 'Gmail',
auth: {
user: 'administrator#radleaf.com',
pass: '34Girls34*goo'
}
});
// NB! No need to recreate the transporter object. You can use
// the same transporter object for all e-mails
// setup e-mail data with unicode symbols
var mailOptions = {
from: 'Fred Foo ✔ <foo#blurdybloop.com>', // sender address
to: 'ty#radleaf.com', // list of receivers
subject: 'Hello ✔', // Subject line
text: 'Hello world ✔', // plaintext body
html: '<b>Hello world ✔</b>' // html body
};
// send mail with defined transport object
transporter.sendMail(mailOptions, function (error, info) {
if (error) {
console.log(error);
} else {
console.log('Message sent: ' + info.response);
}
});
};
Not sure how this work with the HTML with the view:
<form action="mail">...</form>
If I understand the question correctly, you're asking how you can gather data which is input into a form, send that data to expressJS, and then use that data to send an outbound email.
If so, then here is your flow:
Step 1: Create a form in a view and map it to an AngularJS controller
<form name="contactForm" data-ng-submit="sendMail()">
Name: <input type="text" data-ng-model="contact_name">
Message: <input type="text" data-ng-model="contact_msg">
<button type="submit">
</form>
Step 2: In your AngularJS controller, use a $http request to send your data to your Express Route
$scope.sendMail = function() {
// Simple POST request example (passing data) :
$http.post('/mail', {name: contact_name, msg: contact_msg}).
success(function(data, status, headers, config) {
// this callback will be called asynchronously
// when the response is available
}).
error(function(data, status, headers, config) {
// called asynchronously if an error occurs
// or server returns response with an error status.
});
}
Step 3: Use your ExpressJS Route as an API to call your ExpressJS Controller.
(It looks like you've got this bit covered)
app.route('/mail').get(mail.createmail);
Step 4: Receive and do something with the data passed through the $http POST
exports.createmail = function(req, res) {
var data = req.body;
Now you can use the data, like this
var mailOptions = {
from: data.name, // sender name
text: data.msg, // plaintext body
};
MeanJS 0.4.0 also has a working example of NodeMailer which might help: https://github.com/meanjs/mean/tree/0.4.0
With angularJS, you can remove the action attribute, and just use angularJS ngSubmit directive, and call a function in your controller which would now visit the endpoint with $http.get.
The following code for texture upload was working in angularjs 1.2.26 but stopped working when I upgraded my web application to 1.3.0.
HTML Code
<input file-model="file" name="file" type="file">
fileModel Directive
app.directive('fileModel', [ '$parse', function($parse) {
return {
restrict : 'A',
link : function(scope, element, attrs) {
var model = $parse(attrs.fileModel);
var modelSetter = model.assign;
element.bind('change', function() {
scope.$apply(function() {
modelSetter(scope, element[0].files[0]);
});
});
scope.$on("reset", function() {
element.val(null);
});
}
};
} ]);
Upload Function - In 1.2.26 choosing "multipart/form-data" as Content-Type would not work. But with "undefined" as Content-Type angular would do some kind of magic that would make it work.
var upload = function(textureUploadUrl) {
var formData = new FormData();
formData.append('name', $scope.name);
formData.append('file', $scope.file);
$http.post(textureUploadUrl, formData, {
transformRequest : angular.identity,
headers : {
'Content-Type' : undefined
}
}).success(function(uploadedTexture) {
console.log(uploadedTexture);
// further processing of uploadedTexture
});
}
ERROR:
SyntaxError: Unexpected token h
at Object.parse (native)
at fromJson (https://ajax.googleapis.com/ajax/libs/angularjs/1.3.0/angular.js:1104:14)
at defaultHttpResponseTransform (https://ajax.googleapis.com/ajax/libs/angularjs/1.3.0/angular.js:8572:18)
at https://ajax.googleapis.com/ajax/libs/angularjs/1.3.0/angular.js:8517:12
at forEach (https://ajax.googleapis.com/ajax/libs/angularjs/1.3.0/angular.js:335:20)
at transformData (https://ajax.googleapis.com/ajax/libs/angularjs/1.3.0/angular.js:8516:3)
at transformResponse (https://ajax.googleapis.com/ajax/libs/angularjs/1.3.0/angular.js:9224:23)
at processQueue (https://ajax.googleapis.com/ajax/libs/angularjs/1.3.0/angular.js:12914:27)
at https://ajax.googleapis.com/ajax/libs/angularjs/1.3.0/angular.js:12930:27
at Scope.$eval (https://ajax.googleapis.com/ajax/libs/angularjs/1.3.0/angular.js:14123:28)
Does anyone know how to get this working in the new version?
UPDATE
I've narrowed down the problem a little bit. It does not work with 1.3.0-rc.5, but with the previous version (rc.4) it does work. So, I'm currently trying to figure out which changes lead to my problem. Here is the changelog.
The problem was in the response header of my texture upload request on server side. For building my application I used Spring MVC and had a request mapping like that, but without the produces specification:
#RequestMapping(value = "/uploadUrl", method = RequestMethod.POST, produces = "text/plain; charset=utf-8")
#ResponseBody
public String textureUploadPOST(HttpServletRequest req) {
// upload texture and create serveUrl for the texture
return serveUrl;
}
So, without specifying text/plain as Content-Type, Spring automatically uses application/json as Content-Type. And this causes angular.js to invoke JSON.parse since 1.3.0-rc.5 (change). My texture upload returns an URL pointing to the location where the image is served. Because this is a plain String, it resulted in the described error message. So, don't forget to specify correct Content-Types in your server responses :-)
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)