Basic Save form data the Angular way - angularjs

I am trying to figure out the "correct" way to get and set form data using Angular. (By "correct" I mean one that supports scalability to larger apps, where controllers aren't overly simple, with shortcut references, as they are in most demos.) When I look though the examples, the seem so basic (such as no controllerAs specifier) that I can't figure out what's pointing at what.
Anyway, I'm expecting that there is an object somewhere in my viewmodel, accessible by the controller, that contains the data in its named fields, suitable for PUTing to the api - and doesn't contain all the cruft that is associated with the actual form and field objects:
To-wit: this is what settingsVm.settingsForm contains:
$dirty:true
$error:{}
$invalid:false
$name:"settingsVm.settingsForm"
$pristine:false
$submitted:true
$valid:true
Email:"asdsa#asdsad.com"
Id:{$validators: {}, $asyncValidators: {}, $parsers: [], $formatters: [], $viewChangeListeners: [],…}
$asyncValidators:{}
$dirty:false
$error:{}
$formatters:[]
$invalid:false
$name:"Id"
$options:null
$parsers:[]
$pristine:true
$touched:false
$untouched:true
$valid:true
$validators:{}
$viewChangeListeners:[]
StreetDirection:""
StreetName:"asdasdsadsada"
StreetNumber:"34"
Suite:""
Here is my form, with just a couple of fields:
<input type="hidden" name="Id" ng-model="settingsVm.settings.Id" />
<div class="form-group row">
<label for="Email" class="col-sm-1 col-form-label text-right"> Email:</label>
<div class="col-sm-5">
<input type="text" class="form-control form-group-med" maxlength="50" name="Email" ng-model="settingsVm.settingsForm.Email" required />
<div class="error-message" ng-show="settingsVm.settingsForm.Email.$invalid && settingsVm.settingsForm.Email.$touched || settingsVm.settingsForm.submitted">
<span ng-show="settingsVm.settingsForm.Email.$error.required"> Email is required.</span >
</div>
</div>
</div>
<div class="form-group row">
<label for="StreetName" class="col-sm-1 col-form-label text-right"> Name:</label>
<div class="col-sm-5">
<input type="text" class="form-control form-group-med" maxlength="50" name="Email" ng-model="settingsVm.settingsForm.StreetName" required />
<div class="error-message" ng-show="settingsVm.settingsForm.StreetName.$invalid && settingsVm.settingsForm.StreetName.$touched || listingVm.submitted">
<span ng-show="settingsVm.settingsForm.StreetName.$error.required">Street Name is required.</span >
</div>
</div>
</div>
</form>
Am I using the ng-model correctly? And am I using the $error correctly?
Should my model be a distinct object from my form? Maybe my model should point at settingsVm.settingsData instead of settingsVm.settingsForm.
Here is my submit:
vm.submit = function (isValid) {
vm.submitted = true;
if (isValid) {
settingsService.putSettings(vm.settingsForm);
}
};

Not sure if this will help you after 3 months, but I figured it out, please correct me if I am wrong somewhere.
Yes your statement is right, you actually need to separate form validation and input fields.
I assume when you are using field validation angular actually uses input field names not the ng-Model values. So this works for me
var myapp = angular.module("taskApp", [])
.controller("taskCtrl", function($scope, $http) {
$scope.form = {};
$scope.makeTask = function(data) {
console.dir(data);
}
});
<script src="https://ajax.googleapis.com/ajax/libs/jquery/2.1.1/jquery.min.js"></script>
<script src="https://ajax.googleapis.com/ajax/libs/angularjs/1.2.23/angular.min.js"></script>
<div ng-App="taskApp" ng-controller="taskCtrl">
<form name="TaskForm" novalidate ">
<div class="form-group ">
<label>Name</label>
<input name="Name " ng-required="true " class="form-control " ng-model="form.Name " />
<span ng-show="TaskForm.Name.$invalid && TaskForm.Name.$dirty ">The name is required</span>
</div>
<button ng-Click="makeTask(form) " type="submit ">Add</button>
</form>
<div id="output ">OUTPUT</div>

Related

get values from each ng-repeat input elements

I have the following code where I have added 5 input texts via ng-repeat and now am trying to fetch each of the input element's values. I am hitting some roadblock, so please help me out on how to fetch individual values.
HTML:
<form class="quick-add-form" method="post">
<label class="quick-add-label">Enter the catalog number and quantity</label>
<div class="quick-add">
<div class="catalog-list">
<label class="catalog-number-label" for="catalog-number-input">Catalog Number:</label>
<div ng-repeat="values in catalogNumber(number) track by $index">
<input id="catalog-number-input-{{$index}}" class="catalog-number-input"
type="text" ng-model="catalog.description" validate-input>
</div>
</div>
<div class="qnty-list">
<label class="pdt-qnty-label" for="pdt-qnty-input">Quantity:</label>
<div ng-repeat="values in catalogNumber(number) track by $index">
<input id="pdt-qnty-input-{{$index}}" class="pdt-qnty-input" type="number" pattern="[0-9]*"
inputmode="numeric" min="1" max="999" ng-model ="catalog.quantity" name="qty"
maxlength="3">
</div>
</div>
</div>
<div>
<button type="submit" class="btn btn-danger cta-button quick-add-btn"
ng-click ="quickAdd(catalog)">Add to List</button>
</div>
</form>
and controller:
$scope.catalog = {};
$scope.catalogNumber = catalogNumber;
$scope.initializeModal = initializeModal;
$scope.quickAdd = quickAdd;
function catalogNumber(num) {
return new Array(num);
}
function quickAdd(val) {
console.log(val);
}
function init() {
$scope.number = 5;
$scope.catalog.quantity = 1;
}
I am pretty sure its because of indexing and which is why by adding value in one input, its adding same value in all at once. But I am not sure how/where to use the $index properly to achieve the result.
Thanks.
Perhaps a better way would be to use the model? For example:
<div ng-repeat="values in catalogNumber(number) track by $index">
<input
id="catalog-number-input-{{$index}}"
class="catalog-number-input"
type="text"
ng-model="catalog.description[$index]"
validate-input>
</div>
Now in your controller:
catalog.description = [];
Then you have an array of objects, which you can perform your math on:
var total = _.sum(Object.values(catalog.description));
Edit: I had forgot I was using Lodash/Underscore there, but if you wanted to use standard JS you could use a standalone method, or reduce etc.,

Checkbox not sending false value

I have a rails application which use AngularJS and I have a problem with a form, the problem is that I want to use a checkbox to send values true or false, but it only send true if it's checked and false if it's checked and unchecked after that, but if the user doesn't touch the checkbox, then it's not even sent as parameter.
<div class="checkbox">
<label>
<input type="checkbox" ng-model="car"> Do you have a car?
</label>
</div>
What can I do to make it send false if it the user doesn't ever check it?
Edit: The entire form is this, BTW, the form it's about creating a Poll, the car thing was just an example...
<h1>Create Poll</h1>
<form ng-submit="addPoll()" style="margin-top:30px;">
<div class="form-group">
<label>Title</label>
<input type="text" class="form-control" ng-model="title"></input>
</div>
<div class="form-group">
<label>Description</label>
<textarea type="text" class="form-control" ng-model="description"></textarea>
</div>
<br>
<div class="checkbox">
<label>
<input type="checkbox" ng-model="allow_anonymous_answer" ng-false-value="false"> Allow anonymous answers
</label>
</div>
<br>
<div class="form-group">
<label>Welcome message</label>
<textarea type="text" class="form-control" ng-model="initial_message"></textarea>
</div>
<div class="form-group">
<label>Outgoing Message</label>
<textarea type="text" class="form-control" ng-model="final_message"></textarea>
</div>
<button type="submit" class="btn btn-primary" style="float: right;">Continue</button>
</form>
When you hit Continue I make HTTP POST request with Restangular to create a Poll, but the problem is that when I don't touch the checkbox this is what I see in the log of Rails...
Started POST "/polls.json" for 127.0.0.1 at 2016-01-26 14:05:57 -0300
Processing by PollsController#create as JSON
Parameters: {"title"=>"asddddddddddddddda", "description"=>"aaaaaaaaaaaaaaaaaaaaa", "initial_message"=>"asdasdddddddddd", "final_message"=>"aaaaaaaaaaaaaaaaaaad", "poll"=>{"title"=>"asddddddddddddddda", "description"=>"aaaaaaaaaaaaaaaaaaaaa", "initial_message"=>"asdasdddddddddd", "final_message"=>"aaaaaaaaaaaaaaaaaaad"}}
Note that the parameter allow_anonymous_answer doesn't even appear, if I check the checkbox then I can see that the parameter is set as true, if I check it and then uncheck it, then it's set as false, but the problem is when the user doesn't even touch this, when this happens then the parameter is not even shown...
Just in case you wanna see, this is the controller of AngularJS...
angular.module('myapp').controller('CreatePollCtrl', ['$scope', 'Restangular',
function($scope, Restangular) {
Restangular.setFullResponse(true);
$scope.addPoll = function() {
var poll = {title: $scope.title, description: $scope.description, allow_anonymous_answer: $scope.allow_anonymous_answer, initial_message: $scope.initial_message, final_message: $scope.final_message};
Restangular.all('polls').post(poll).then(function(response) {
});
};
}]);
I think you should put a variable in your controller to achieve the binding between your HTML component and your JS code.
I am currently developing an Angular app, and what i do is to initialize all the ng-model variables in the first lines of my controller, so why dont you give a try to this:
In your first controllers lines:
$scope.allow_anonymous_answer = false;
Did you take a look at angular docs: https://docs.angularjs.org/api/ng/input/input[checkbox]
You can explicitly state what value the checkbox should send when it is not selected using ng-false-value
Add an ng-click to that checkbox and update the model there. Works fine.
<div class="checkbox">
<label>
<input type="checkbox" ng-model="car" ng-click="updateCar(this)">Do you have a car?</input>
</label>
</div>
In your controller:
var updateCar = function(checkbox) {
if (checkbox.checked) {
car = false;
}
else {
car = true;
}
}
I solved it...
In the controller
if ($scope.allow_anonymous_answer == null)
$scope.allow_anonymous_answer = false

AngularJS - how to sync result of calculated input field to a scope variable

I'm trying to sync the result of a calculated form field into a scope variable. For example:
<div class="form-group">
<label for="val1" class="control-label">Val 1</label>
<input class="form-control" name="val1" type="number" ng-model="data.val1" id="val1">
</div>
<div class="form-group">
<label for="val2" class="control-label">Val 2</label>
<input class="form-control" name="val2" type="number" ng-model="data.val2" id="val2">
</div>
<div class="form-group">
<label for="score" class="control-label">Score</label>
<input class="form-control" name="score" value="{{data.val1+data.val2}}" type="text" id="score">
</div>
<br/>data: {{data}}
How can I sync the result (i.e. the score field) into the scope variable $scope.data.score? I have tried ng-model="data.score" but that breaks the calculation.
You can see the example in action here:
http://plnkr.co/edit/fc9XcyyYGtAk0aGVV35t?p=preview
How do I get the last line to read data: {"val1":1,"val2":2,"score":3}?
Note that I'm looking for a solution that involves minimal to no code support at the controller level. For example, I know you can set up a watch in the controller for both val1 and val2 and then update the score in the watcher. This is what I wanted to avoid, if it's possible in angular at all. If it's not (theoretically) possible, I'd really appreciate an explanation of why it's not.
A quick background might help. Basically we have a simple form builder app that defines a form and all its fields in an xml file. Here's a sample of what the xml would look like:
<form name="test">
<field name="val1" control="textbox" datatype="number">
<label>Val 1</label>
</field>
<field name="val2" control="textbox" datatype="number">
<label>Val 2</label>
</field>
<field name="score" control="textbox" datatype="number">
<label>Score</label>
<calculate>[val1]+[val2]</calculate>
</field>
</form>
When a form is requested, the system will need to pick up the xml, loop through all the fields and generate an angular style html to be served to the browser and processed by angular. Ideally, I want to keep all the form specific logic (validation, show/hide, calculation etc) confined to the html, and keep the controller (js) logic generic for all forms.
The only solution I can come up with is to dynamically load watcher functions, through something like this: eval("$scope.$watch('[data.val1,data.val2]')..."), but as I said, I really want to avoid this, because it's just tedious, and feels extremely dodgy :)
The first dirty way.
In your case you can move all logic from controller into html with using combination of ng-init and ng-change directives.
<div class="form-group">
<label for="val1" class="control-label">Val 1</label>
<input class="form-control" name="val1" type="number" ng-model="data.val1" ng-change="data.score = data.val1 + data.val2" id="val1">
</div>
<div class="form-group">
<label for="val2" class="control-label">Val 2</label>
<input class="form-control" name="val2" type="number" ng-model="data.val2" ng-change="data.score = data.val1 + data.val2" id="val2">
</div>
<div class="form-group" ng-init="data.score = data.val1 + data.val2">
<label for="score" class="control-label">Score</label>
<input class="form-control" name="score" ng-model="data.score" type="text" id="score">
</div>
<br/>data: {{data}}
I don't think that it's the clearest solution, but you can leave your controller without any changes with it.
Demo on plunker.
The second dirty way.
There is one more way, but now you don't need ng-init and ng-change directives. You can add just one dirty expression in html:
<div id="main-container" class="container" style="width:100%" ng-controller="MainController">
{{data.score = data.val1 + data.val2;""}} <!-- key point -->
<div class="form-group">
<label for="val1" class="control-label">Val 1</label>
<input class="form-control" name="val1" type="number" ng-model="data.val1" id="val1">
</div>
<div class="form-group">
<label for="val2" class="control-label">Val 2</label>
<input class="form-control" name="val2" type="number" ng-model="data.val2" id="val2">
</div>
<div class="form-group">
<label for="score" class="control-label">Score</label>
<input class="form-control" name="score" ng-model="data.score" type="text" id="score">
</div>
<br/>data: {{data}}
;"" in expression stops evaluating of angular expression to text in html.
Demo on plunker.
See if this works, in your HTML change,
<input class="form-control" name="score" ng-model = "data.score" type="text" id="score">
and, in your controller do,
var myApp = angular.module('myapp', [])
.controller('MainController', function($scope) {
$scope.data = { val1: 1, val2: 2, score: 3};
$scope.$watch('[data.val1,data.val2]', function (newValue, oldValue) {
$scope.data.score = newValue[0] + newValue[1];
}, true);
})
Demo plunk, http://plnkr.co/edit/gS0UenjydgId4H5HwSjL?p=preview
If you want to know how you can do it, then i have one solution for you make a ng-change event for both of your text box and sum both the number there and use ng-model in the third text box then, you can see it will work as per your need.
For the first time load you need to calculate it out side only.

Ng-model does not update view

I have searched thoroughly everywhere and can't seem to find the solution to my problem.
I am trying to make a form to fill in to create new task object onto projects on the website like a scrum backlog or something in Angular and angular-ui.
I use Angular-UI for typeahead functionality for when adding team members to a task, so available members on a project pop-up. I have seen guys with similar problems, but nothing seems to solve it for me. Below is the HTML and the controller. Sorry for the long markup, but I suspect it has to do something with nesting the controllers and mixing the scopes, so I'm including everything relevant.
<div ng-controller="SubmitCreateTaskController">
<div class="modal fade" id="taskModal" tabindex="-1" role="dialog" aria-labelledby="myModalLabel" aria-hidden="true">
<div class="modal-dialog">
<div class="modal-content">
<div class="modal-header">
<button type="button" class="close" data-dismiss="modal">
<span aria-hidden="true">×</span>
<span class="sr-only">Close</span>
</button>
<h4 class="modal-title" id="myModalLabel">New Task</h4>
</div>
<div class="modal-body">
<form role="form" method="post" name="create-task">
<div class="form-group">
<label for="taskname">Task Name</label>
<input type="text" class="form-control" id="taskname" ng-model="taskForm.taskName" placeholder="Task Name">
</div>
<div class="form-group">
<label for="taskdesc">Description</label>
<textarea class="form-control" id="taskdesc" size="3" ng-model="taskForm.taskDescription" placeholder="Enter a short description here..." rows="2"></textarea>
</div>
<div class="form-group">
<label for="assigneddate">Assigned Date</label>
<input type="text" class="form-control" id="assigneddate" ng-model="taskForm.assignedDate">
</div>
<div ng-controller="TypeAheadController">
<div class="form-group">
<label for="contributors">Add Contributors</label>
<input id="contributors" type="text" class="form-control" ng-model="contrib.selected" typeahead="member for member in contrib.stream_members | filter:$viewValue"
typeahead-editable="false" typeahead-on-select="contrib.onSelect($item)">
</div>
<div class="form-group">
<label for="users">Contributors:</label>
<textarea class="form-control" disabled="disabled" id="users" ng-model="contrib.entered" rows="1"></textarea>
</div>
</div>
<div class="form-group">
<label for="taskcomments">Comments</label>
<textarea class="form-control" id="taskcomments" ng-model="taskForm.comment" placeholder="Comments" rows="2"></textarea>
</div>
<div class="modal-footer">
<button type="submit" class="btn btn-primary" ng-click="taskForm.submit()">Add Task</button>
<button type="button" class="btn btn-danger" data-dismiss="modal">Cancel</button>
</div>
</form>
</div>
</div>
</div>
</div>
Controllers:
.controller('SubmitCreateTaskController', ['$scope', '$http', function($scope, $http) {
$scope.taskForm = {};
$scope.taskForm.taskName = '';
$scope.taskForm.taskDescription = '';
$scope.taskForm.assignedDate = new Date();
$scope.taskForm.contributors = [];
$scope.taskForm.comment = '';
$scope.taskForm.submit = function(item, event) {
var formData = {name: $scope.taskForm.taskName,
description: $scope.taskForm.taskDescription,
status: 'open',
assigned: $scope.taskForm.assignedDate,
completed: null,
contributors: $scope.taskForm.contributors,
comments: [{body: $scope.taskForm.comment,
user: 'RACHE User',
date: new Date()
}]
};
var postURL = '../create_task/' + $scope.stream_name;
$http.post(postURL, formData)
.success(function(){
taskForm = {}; // empty the form of previous input values
alert('New Task Created for ' + $scope.stream_name);
})
.error(function(res) {
alert(res.data);
});
};
}])
.controller('TypeAheadController', ['$scope', '$http', function($scope, $http) {
$scope.contrib = {};
$scope.contrib.selected = '';
$scope.contrib.stream_members = undefined;
$scope.contrib.entered = [];
$http.get('/stream_members/' + $scope.stream_name)
.then(function(res) {
$scope.contrib.stream_members = res.data.stream_members;
});
$scope.contrib.onSelect = function($item) {
$scope.contrib.selected = '';
$scope.contrib.entered.push($item);
console.log($scope.contrib.entered);
};
This last bit is the important part in 'TypeAheadController'. I am trying to add the poped-up team members to a textarea just below which would be sent to the DB later. That is why I have bound the actual typeahead input box to 'contrib.select' and the "display/post" box to 'contrib.entered', so the search can be continued after adding one member, the search box is cleared, selected member is added to box below, new search can begin. In the mark up I call the last (onSelect) function in the typeahead-on-select callback. This so that I can clear the input box and another member can be easily added without deleting manually. IN this function the entered member should be appended to the array that is bound to below "display/post" box. This happens, since the console log shows good value, the search/input field gets cleared, so the function gets called as well.
The view does not get updated with the updated array bound to display box however. I have tried EVERYTHING. I have googled around and found it here that dot notation needs to be used as Angular can't update with primitives, but this hasn't helped either.
Everything works fine, the logic is good, values are good when I print them out, pop-up works and clears up as expected, but The damn textarea under it does not get updated.
Any suggestions? I have been stuck on this for almost a day now and I am really frustrated by this. I would be ever so greatfull for any help!
Thanks guys in advance!
Textarea do use the value attribute, your code isn't working because contrib. entered isn't between the tags, but because you can not bind the textarea to an array (it needs to bind to a string).
look at this plunker, if you do this in your html:
<textarea class="form-control" disabled="disabled" id="users" rows="1" ng-model="contrib.enteredString"></textarea>
and add this line at the end of your typeahead controller:
$scope.contrib.enteredString = $scope.contrib.entered.toString();
then the text area will update
Textareas don't use the ng-value as their value is contained within the tag. Remove the ng-model and put the value between the tags.
<textarea class="form-control" disabled="disabled" id="users" rows="1">{{contrib.entered}}</textarea>

Binding repeatable form subset to model

On a registration form I allow a user to enter zero or more phone numbers. Each phone number consists of a prefix and a number, these are two fields per phone number. The user may decide how many numbers he wants to provide, the add more numbers link would clone this part of the form.
Prefix: [_______]
Number: [_______]
+ add more numbers
The model I bind to is fixed and should be this format:
$scope.model = {
"...main inputs": "username, etc...",
phoneNumbers: [
// for each phone number I expect this object
{ "prefix": "+1", "number": "123123123" }
]
};
I am not sure how I should set up my ng-model for these text inputs to have these objects generated within the array.
Also, I am a great fan of referential binding and limiting things like scope watch and event-based changes to the scope, as often these changes go unnoticed for other directives that may be using this value (unless being watched). Basically that means it is my intention to have dynamic generation of objects within the array as the form is filled in with numbers, or dynamic removal of objects within the array as numbers are removed or both inputs left empty.
The array should only contain valid and filled objects, partially filled objects or empty objects should not be added to the model or array (as usually is done with properties with invalid values -- those properties get removed from the model object). Basically a push to the array with every validated object, and removal (slice) of objects for every invalid object. But then rather automatically, instead of writing a push/slice function.
Hi try out this code:
Html:
<form name="form">
<div data-ng-repeat="phone in phoneNumberArray">
<div class="form-group">
<label for="inputFirstName">Prefix</label>
<input id="prefix" class="form-control" type="text" ng-model="phone.prefix">
</div>
<div class="form-group">
<label for="inputLastName">Phone number</label>
<input id="phoneNumber" class="form-control" type="text" ng-model="phone.phoneNumber">
</div>
</div>
</form>
<div class="form-group">
<input type="submit" class="btn btn-primary" value="Add more number" data-ng-click="addmore()"/>
</div>
Controller:
$scope.phoneNumberArray = [
{
prefix: "",
phoneNumber:"",
}];
$scope.addmore = function () {
$scope.phoneNumberArray.push({
prefix: "",
phoneNumber: "",
});
}
Hi check this demo in Fiddle
Use the following code.....
<!DOCTYPE html>
<html>
<head>
<script src= "http://ajax.googleapis.com/ajax/libs/angularjs/1.2.26/angular.min.js"></script>
</head>
<body ng-app ng-controller="todoCtrl">
<form name="form" ng-submit="addmore()">
<div class="form-group">
<label for="inputFirstName">Prefix</label>
<input class="form-control" type="text" ng-model="phone_prefix"/>
</div>
<div class="form-group">
<label for="inputLastName">Phone number</label>
<input class="form-control" type="text" ng-model="phone_Number"/>
</div>
<div class="form-group">
<input type="submit" class="btn btn-primary" value="Add more number"/>
</div>
</form>
<div ng-repeat="phone in phoneNumberArray">
{{ phone.prefix + " "+ phone.phoneNumber}}
</div>
<script>
function todoCtrl($scope) {
debugger;
$scope.phoneNumberArray = [
{prefix: '14', phoneNumber:'123'}
];
$scope.addmore = function () {
$scope.phoneNumberArray.push(
{prefix: $scope.phone_prefix, phoneNumber: $scope.phone_Number}
);
}
}
</script>
</body>
</html>

Resources