I'm a beginner to angular.js but I have a good grasp of the basics.
What I am looking to do is upload a file and some form data as multipart form data. I read that this isn't a feature of angular, however 3rd party libraries can get this done. I've cloned angular-file-upload via git, however I am still unable to post a simple form and a file.
Can someone please provide an example, html and js of how to do this?
First of all
You don't need any special changes in the structure. I mean: html input tags.
<input accept="image/*" name="file" ng-value="fileToUpload"
value="{{fileToUpload}}" file-model="fileToUpload"
set-file-data="fileToUpload = value;"
type="file" id="my_file" />
1.2 create own directive,
.directive("fileModel",function() {
return {
restrict: 'EA',
scope: {
setFileData: "&"
},
link: function(scope, ele, attrs) {
ele.on('change', function() {
scope.$apply(function() {
var val = ele[0].files[0];
scope.setFileData({ value: val });
});
});
}
}
})
In module with $httpProvider add dependency like ( Accept, Content-Type etc) with multipart/form-data. (Suggestion would be, accept response in json format)
For e.g:
$httpProvider.defaults.headers.post['Accept'] = 'application/json, text/javascript';
$httpProvider.defaults.headers.post['Content-Type'] = 'multipart/form-data; charset=utf-8';
Then create separate function in controller to handle form submit call.
like for e.g below code:
In service function handle "responseType" param purposely so that server should not throw "byteerror".
transformRequest, to modify request format with attached identity.
withCredentials : false, for HTTP authentication information.
in controller:
// code this accordingly, so that your file object
// will be picked up in service call below.
fileUpload.uploadFileToUrl(file);
in service:
.service('fileUpload', ['$http', 'ajaxService',
function($http, ajaxService) {
this.uploadFileToUrl = function(data) {
var data = {}; //file object
var fd = new FormData();
fd.append('file', data.file);
$http.post("endpoint server path to whom sending file", fd, {
withCredentials: false,
headers: {
'Content-Type': undefined
},
transformRequest: angular.identity,
params: {
fd
},
responseType: "arraybuffer"
})
.then(function(response) {
var data = response.data;
var status = response.status;
console.log(data);
if (status == 200 || status == 202) //do whatever in success
else // handle error in else if needed
})
.catch(function(error) {
console.log(error.status);
// handle else calls
});
}
}
}])
<script src="//unpkg.com/angular/angular.js"></script>
This is pretty must just a copy of that projects demo page and shows uploading a single file on form submit with upload progress.
(function (angular) {
'use strict';
angular.module('uploadModule', [])
.controller('uploadCtrl', [
'$scope',
'$upload',
function ($scope, $upload) {
$scope.model = {};
$scope.selectedFile = [];
$scope.uploadProgress = 0;
$scope.uploadFile = function () {
var file = $scope.selectedFile[0];
$scope.upload = $upload.upload({
url: 'api/upload',
method: 'POST',
data: angular.toJson($scope.model),
file: file
}).progress(function (evt) {
$scope.uploadProgress = parseInt(100.0 * evt.loaded / evt.total, 10);
}).success(function (data) {
//do something
});
};
$scope.onFileSelect = function ($files) {
$scope.uploadProgress = 0;
$scope.selectedFile = $files;
};
}
])
.directive('progressBar', [
function () {
return {
link: function ($scope, el, attrs) {
$scope.$watch(attrs.progressBar, function (newValue) {
el.css('width', newValue.toString() + '%');
});
}
};
}
]);
}(angular));
HTML
<form ng-submit="uploadFile()">
<div class="row">
<div class="col-md-12">
<input type="text" ng-model="model.fileDescription" />
<input type="number" ng-model="model.rating" />
<input type="checkbox" ng-model="model.isAGoodFile" />
<input type="file" ng-file-select="onFileSelect($files)">
<div class="progress" style="margin-top: 20px;">
<div class="progress-bar" progress-bar="uploadProgress" role="progressbar">
<span ng-bind="uploadProgress"></span>
<span>%</span>
</div>
</div>
<button button type="submit" class="btn btn-default btn-lg">
<i class="fa fa-cloud-upload"></i>
<span>Upload File</span>
</button>
</div>
</div>
</form>
EDIT: Added passing a model up to the server in the file post.
The form data in the input elements would be sent in the data property of the post and be available as normal form values.
It is more efficient to send the files 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:
Doing Multiple $http.post Requests Directly from a FileList
$scope.upload = function(url, fileList) {
var config = {
headers: { 'Content-Type': undefined },
transformResponse: angular.identity
};
var promises = fileList.map(function(file) {
return $http.post(url, file, config);
});
return $q.all(promises);
};
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.
Working Demo of "select-ng-files" Directive that Works with ng-model1
The <input type=file> element does not by default work with the ng-model directive. It needs a custom directive:
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="fileList" multiple>
<h2>Files</h2>
<div ng-repeat="file in fileList">
{{file.name}}
</div>
</body>
You can check out this method for sending image and form data altogether
<div class="form-group ml-5 mt-4" ng-app="myApp" ng-controller="myCtrl">
<label for="image_name">Image Name:</label>
<input type="text" placeholder="Image name" ng-model="fileName" class="form-control" required>
<br>
<br>
<input id="file_src" type="file" accept="image/jpeg" file-input="files" >
<br>
{{file_name}}
<img class="rounded mt-2 mb-2 " id="prvw_img" width="150" height="100" >
<hr>
<button class="btn btn-info" ng-click="uploadFile()">Upload</button>
<br>
<div ng-show = "IsVisible" class="alert alert-info w-100 shadow mt-2" role="alert">
<strong> {{response_msg}} </strong>
</div>
<div class="alert alert-danger " id="filealert"> <strong> File Size should be less than 4 MB </strong></div>
</div>
Angular JS Code
var app = angular.module("myApp", []);
app.directive("fileInput", function($parse){
return{
link: function($scope, element, attrs){
element.on("change", function(event){
var files = event.target.files;
$parse(attrs.fileInput).assign($scope, element[0].files);
$scope.$apply();
});
}
}
});
app.controller("myCtrl", function($scope, $http){
$scope.IsVisible = false;
$scope.uploadFile = function(){
var form_data = new FormData();
angular.forEach($scope.files, function(file){
form_data.append('file', file); //form file
form_data.append('file_Name',$scope.fileName); //form text data
});
$http.post('upload.php', form_data,
{
//'file_Name':$scope.file_name;
transformRequest: angular.identity,
headers: {'Content-Type': undefined,'Process-Data': false}
}).success(function(response){
$scope.IsVisible = $scope.IsVisible = true;
$scope.response_msg=response;
// alert(response);
// $scope.select();
});
}
});
Related
I have created an application managing contacts. The user can add a contact. After filling the name, I would like to check if the value already exists in the DB.
Can you please help for doing that?
I have created a new field username and I created a directive but I don't know if this way is the best solution. The query is correctly executed. But I improve some difficulties for displaying the results "username exists already" (during the loading it's correctly displayed "checking.....").
Here the file app.js (with the module and the controler "ctrlContacts"):
var app=angular.module('ContactsApp', ['ngRoute', 'ui.bootstrap', 'ngDialog']);
// register the interceptor as a service
app.factory('HttpInterceptor', ['$q', '$rootScope', function($q, $rootScope) {
return {
// On request success
request : function(config) {
// Return the config or wrap it in a promise if blank.
return config || $q.when(config);
},
// On request failure
requestError : function(rejection) {
//console.log(rejection); // Contains the data about the error on the request.
// Return the promise rejection.
return $q.reject(rejection);
},
// On response success
response : function(response) {
//console.log(response); // Contains the data from the response.
// Return the response or promise.
return response || $q.when(response);
},
// On response failure
responseError : function(rejection) {
//console.log(rejection); // Contains the data about the error.
//Check whether the intercept param is set in the config array.
//If the intercept param is missing or set to true, we display a modal containing the error
if (typeof rejection.config.intercept === 'undefined' || rejection.config.intercept)
{
//emitting an event to draw a modal using angular bootstrap
$rootScope.$emit('errorModal', rejection.data);
}
// Return the promise rejection.
return $q.reject(rejection);
}
};
}]);
// MY DIRECTIVE FOR CHECKING IF THE USERNAME IS ALREADY USED
app.directive('usernameAvailable', function($timeout, $q, $http, ContactService) {
return {
restrict: 'AE',
require: 'ngModel',
link: function(scope, elm, attr, ngModel) {
ngModel.$asyncValidators.usernameExists = function() {
return ContactService.searchContactByName('ADAM').success(function(contact){
$timeout(function(){
ngModel.$setValidity('usernameExists', contact);
ngModel.$setValidity('unique', false);
scope.contacts = contact;
alert(contact.length);
}, 1000);
});
};
}
}
});
app.controller('ctrlAddContacts', function ($scope, ContactService){
$scope.title="Add a contact";
ContactService.getCountry().success(function(countries){
$scope.countries = countries;
});
ContactService.loadCategory('undefined',0).success(function(categories){
$scope.categories = categories;
});
$scope.Category = function (contactType) {
if (contactType){
ContactService.loadCategory(contactType,0).success(function(categories){
$scope.categories = categories;
});
}
}
$scope.submitForm = function(contact){
if($scope.ContactForm.$valid){
ContactService.addNewPerson(contact).success(function(Person){
$scope.ContactForm.$setPristine();
$scope.contact= Person;
var personID = Person[0]["ID"];
window.location="#/view-contacts/" + personID;
});
}
}
});
the file for the factories: "appServices.js":
app.factory('ContactService', function($http){
var factory={};
factory.searchContactByName=function(string){
if (string){
chaine='http://myapp/contacts.cfc?method=searchContactByName&contactName=' + string;
}else{
chaine='';
}
//alert(chaine);
return $http.get(chaine);
};
return factory;
})
the file for my view "manageContact.html":
<h3>{{title}}</h3>
<div class="panel panel-default">
<div class="panel-heading">
<div class="panel-title">Person Sheet</div>
</div>
<div class="panel-body">
<form name="ContactForm" class="form-horizontal" role="form" novalidate ng-submit="submitForm(contact)">
<!--------------------- USERNAME FIELD AND CHECK IF IT EXISTS ------------------START-->
<div>
<input type="text"
name="username"
ng-model="username"
username-available
required
ng-model-options="{ updateOn: 'blur' }">
<div ng-if="ContactForm.$pending.usernameExists">checking....</div>
<div ng-if="ContactForm.$error.usernameExists">username exists already</div>
</div>
<!---------------------- USERNAME FIELD AND CHECK IF IT EXISTS --------------------END-->
<div class="form-group">
<label for="txtLastName" class="col-sm-2 control-label">Last Name *</label>
<div class="col-sm-10">
<input type="text" class="form-control" name="txtLastName" maxlength="100" placeholder="Enter Last Name" required ng-model="contact.LASTNAME">
</div>
</div>
<div class="form-group">
<div class="col-sm-offset-2 col-sm-10">
<input type="submit" class="btn btn-primary" value="Submit" ng-disabled="ContactForm.$invalid">
Cancel
</div>
</div>
</form>
</div>
</div>
Thank you in advance for your help.
Regards,
it should be
<div ng-if="ContactForm.$pending.usernameExists">checking....</div>
<div ng-if="ContactForm.username.$error.unique">username exists already</div>
Use ng-show and ng-hide instead of ng-if
<div ng-show="ContactForm.$pending.usernameExists">checking....</div>
<div ng-show="ContactForm.$error.usernameExists">username exists already</div>
I'm trying to add a signup function, using a controller and a factory, to my Angular app, but I haven't been able to get several strings (tied conditionally to success or failure) to return from my factory to my controller.
The return statements below only return empty strings at first (I assume this has to do with the asynchronous http, but am not sure). In any case, how would I return the two strings I desire (_alertType and _alertMessage) with the updated values from .success or .error?
signup.html
<div class="col-md-6 container-fluid">
<div class="jumbotron text-center" ng-controller="SignupController as vm">
<p class="lead">
<h2>Account Creation</h2>
Welcome! Please make an account
</p>
<form ng-submit="vm.signup()">
<p><input type="text" name="username" value="" placeholder="Username or Email" ng-model="username"></p>
<p><input type="password" name="password" value="" placeholder="Password" ng-model="password"></p>
<p class="submit"><input type="submit" name="commit" value="Sign Up"></p>
<alert ng-show="vm.alertMessage" type="{{ vm.alertType }}">{{ vm.alertMessage }}</alert>
</form>
</div>
</div>
signup.factory.js
(function() {
angular
.module('app')
.factory('signupFactory', signupFactory);
signupFactory.$inject = ['$http'];
function signupFactory($http) {
var _alertType = '';
var _alertMessage = '';
var service = {
signup: signup,
getAlertType: getAlertType,
getAlertMessage: getAlertMessage
};
return service;
function signup(username, password) {
var request = $http({
method: 'POST',
url: 'http://localhost:8080/user',
headers: {'Content-Type': 'application/x-www-form-urlencoded'},
data: {
username: username,
password: password
}
});
request.success(function(){
_alertType = "success";
_alertMessage = "Signed Up";
});
request.error(function(){
_alertType = "danger";
_alertMessage = "Signup Failed";
});
}
function getAlertType() {
return _alertType;
}
function getAlertMessage() {
return _alertMessage;
}
}
})();
signup.controller.js
(function() {
'use strict';
angular
.module('app')
.controller('SignupController', SignupController);
SignupController.$inject = ['$scope', 'signupFactory'];
function SignupController($scope, signupFactory) {
var vm = this;
vm.signup = function() {
signupFactory.signup($scope.username, $scope.password);
vm.alertMessage = signupFactory.getAlertMessage();
vm.alertType = signupFactory.getAlertType();
}
}
})();
You should look for promises
var promise = asyncGreet('Robin Hood');
promise.then(function(greeting) {
alert('Success: ' + greeting);
}, function(reason) {
alert('Failed: ' + reason);
});
Ive built a rest-API to add todos in a mongodb. I can successfully save instances by using the following setup in postman:
http://localhost:3000/api/addtodo x-www-form-urlencoded with values text="Test", completed: "false".
Now when I try to replicate this with Angular, it doesnt work, the todo is saved but without the text and completed attributes, I cant seem to access the text or completed values from body. What am I doing wrong? Code below:
Angular-HTML:
<div id="todo-form" class="row">
<div class="col-sm-8 col-sm-offset-2 text-center">
<form>
<div class="form-group">
<!-- BIND THIS VALUE TO formData.text IN ANGULAR -->
<input type="text" class="form-control input-lg text-center" placeholder="I want to buy a puppy that will love me forever" ng-model="formData.text">
</div>
<!-- createToDo() WILL CREATE NEW TODOS -->
<button type="submit" class="btn btn-primary btn-lg" ng-click="createTodo()">Add</button>
</form>
</div>
</div>
Angular-js:
$scope.createTodo = function() {
$http.post('/api//addtodo', $scope.formData)
.success(function(data) {
$scope.formData = {}; // clear the form so our user is ready to enter another
$scope.todos = data;
console.log(data);
})
.error(function(data) {
console.log('Error: ' + data);
});
};
REST-API:
router.post('/addtodo', function(req,res) {
var Todo = require('../models/Todo.js');
var todo = new Todo();
todo.text = req.body.text;
todo.completed = req.body.completed;
todo.save(function (err) {
if(!err) {
return console.log("created");
} else {
return console.log(err);
}
});
return res.send(todo);
});
$http.post sends it's data using application/json and not application/x-www-form-urlencoded. Source.
If you're using body-parser, make sure you've included the JSON middleware.
app.use(bodyParser.json());
Either that or change your default headers for angular.
module.run(function($http) {
$http.defaults.headers.post = 'application/x-www-form-urlencoded';
});
I am having trouble trying to append a new url parameter after selecting a genre to create a request to the API.
My ng-change is genreChange. When it been selected, it should automatically append the new url parameter like this &with_genre=fiction in the $scope.movies url before submitting the form with submitButton
<form ng-submit="submitButton()" name="cForm" class="form-horizontal">
<h2>Discover the gems</h2>
<div class="form-group">
<select class="form-control" ng-model="genreSelect" ng-change="genreChange()">
<option ng-repeat="genre in genreList.genres" value="{{genre.id}}">{{genre.name}}</option>
</select>
</div>
<input type="submit" value="hello" />
</form>
-
$scope.genreChange = function() {
$scope.genreVar = $scope.genreSelect;
$scope.movies.get({with_genres: $scope.genreVar});
}
$scope.movies = $resource('http://api.themoviedb.org/3/discover/movie:action', {
api_key: apikey,
callback: 'JSON_CALLBACK'
}, {
get: {
method: 'JSONP'
}
});
$scope.submitButton = function() {
$scope.films = $scope.movies.get();
}
I am doing this method just in case a user leaves it blank.
Thanks
This is what Resource.bind does.
var MovieResource = $resource('http://api.themoviedb.org/3/discover/movie:action', {
api_key: apikey,
callback: 'JSON_CALLBACK'
}, {
get: {
method: 'JSONP'
}
});
$scope.movies = MovieResource;
$scope.genreChange = function() {
$scope.genreVar = $scope.genreSelect;
$scope.movies = MovieResource.bind({with_genres: $scope.genreVar})
}
$scope.submitButton = function() {
$scope.films = $scope.movies.get();
}
See solution below:
I'm trying to connect to a Parse.com Rest backend and display data from object values.
HTML (I put several angular calls to be sure to catch output):
<div ng-controller="MyController">
<p>{{item}}<p>
<p>{{items}}<p>
<p>{{item.firstName}}<p>
<p>{{data}}<p>
</div>
JAVASCRIPT rest:
function MyController($scope, $http) {
$scope.items = [];
$scope.getItems = function() {
$http({method : 'GET',url : 'https://api.parse.com/1/classes/Professional/id', headers: { 'X-Parse-Application-Id':'XXXX', 'X-Parse-REST-API-Key':'YYYY'}})
.success(function(data, status) {
$scope.items = data;
})
.error(function(data, status) {
alert("Error");
});
};
}
This won't work, it does strictly nothing, not even a message in the console.
I know the rest call got the correct credential, as I'm able to get object content returned when I test it with a rest tester program. Maybe the URL should not be absolute ?
Any clue is very welcome, i've spent DAYS on that.
SOLUTION:
Thanks to the help of people answering this thread, I was able to find the solution to this problem so I just wanted to contribute back:
Get Json object data from Parse.com backend, pass it authentification parameters:
function MyController($scope, $http) {
$scope.items = [];
$scope.getItems = function() {
$http({method : 'GET',url : 'https://api.parse.com/1/classes/Professional', headers: { 'X-Parse-Application-Id':'XXX', 'X-Parse-REST-API-Key':'YYY'}})
.success(function(data, status) {
$scope.items = data;
})
.error(function(data, status) {
alert("Error");
});
};
Notice that ' ' necessary arround header key object values. Those ' ' are not necessary around method and url keys.
Template that list all 'firstName' of each object:
<div ng-controller="MyController" ng-init="getItems()">
<ul>
<li ng-repeat="item in items.results"> {{item.firstName}} </li>
</ul>
</div>
Notice: "item in items.results". "results" is necessary because the return value is a JSON object that contains a results field with a JSON array that lists the objects. This could save you some headache.
Also notice "ng-init": if you don't put that, or any other form of call to the getItem(),then nothing will happen and you will be returned no error.
That was my first try of Angularjs, and i'm already in love ^^.
Based in your request the controller should be:
HTML
<div ng-controller="MyController">
<button type="button" ng-click="getItems()">Get Items</button>
<ul>
<li ng-repeat="item in items"> item.firstName </li>
</ul>
</div>
JS
function MyController($scope, $http) {
$scope.items = []
$scope.getItems = function() {
$http({method : 'GET',url : 'https://api.parse.com/1/classes/Users', headers: { 'X-Parse-Application-Id':'XXXXXXXXXXXXX', 'X-Parse-REST-API-Key':'YYYYYYYYYYYYY'}})
.success(function(data, status) {
$scope.items = data;
})
.error(function(data, status) {
alert("Error");
})
}
}
Just a little update to the newer versions of Angular (using .then since version 1.5):
myApp.controller('MyController', function($scope, $http) {
$scope.items = []
$http({
method: 'GET',
url: 'https://api.parse.com/1/classes/Users',
headers: {'X-Parse-Application-Id':'XXXXXXXXXXXXX', 'X-Parse-REST-API-Key':'YYYYYYYYYYYYY'}
})
.then(function successCallback(response) {
alert("Succesfully connected to the API");
$scope.items = data;
}, function errorCallback(response) {
alert("Error connecting to API");
});
});
var app = angular.module("app",[]);
app.controller("postcontroller", function($scope, $http){
$scope.getAllProjects = function() {
var url = 'https://reqres.in/api/products';
$http.get(url).then(
function(response) {
$scope.projects = response.data.data;
},
function error(response) {
$scope.postResultMessage = "Error with status: "
+ response.statusText;
});
}
$scope.getAllProjects();
});
<div ng-app="app">
<div ng-controller="postcontroller">
<div class="panel-body">
<div class="form-group">
<label class="control-label col-sm-2" for="project">Project:</label>
<div class="col-sm-5">
<select id="projectSelector" class="form-control">
<option id="id" ng-repeat="project in projects"
value="{{project.id}}">{{project.name}}</option>
</select>
</div>
</div>
</div>
</div>
</div>
<script src="//ajax.googleapis.com/ajax/libs/angularjs/1.2.16/angular.js"></script>