Angular best way to watch two form fields - angularjs

I have an array of persons, each one should have phone and name,
so I did :
<div ng-repeat="a in arr">
<ng-form>
phone{{a}}:<input type="phone" name="phone" />
text{{a}}:<input type="text" name="name" />
</ng-form>
</div>
When finish to enter phone and name for the first person, I want to call "doSomething" function .
What is the best way to do that? I prefer to not use watch.
Here is a plunker for example.

I'd suggest you to use ng-blur in your case. Using ng-keyup will trigger the function on every key release event, which is not good at all. You should check below example example how it works
phone{{a}}:<input type="phone" data-ng-blur="doSomething();" name="phone" />
I've updated your fiddle to check more.
EDIT
If you only want to execute for the first iteration then you should pass index to your function and check if its equals to 0. Then it must be for first iteration. like
ng-blur="doSomething($index)"
In your function
$scope.doSomething = function(index){
if(index === 0)
alert("finish");
};

one of the ways to achieve this is
<input type="phone" name="phone" ng-model="input.phone" ng-change="onchange()"/>
<input type="text" name="name" ng-model="input.name" ng-change="onchange()"//>
and then on your scope
scope.input = {};
scope.onchange = function(){
if(!input.phone) return;
if(!input.name) return;
//other logic
}
btw, best practises says always have a dot in your ng-model

Probably, you're looking for the following.
HTML
<div ng-repeat="a in arr">
<ng-form>
phone{{a}}:<input type="phone" name="phone" ng-change="doSomething()" />
text{{a}}:<input type="text" name="name" ng-change="doSomething()"/>
</ng-form>
</div>
Controller:
$scope.doSomething= function () {
// do something
};
Note: You can also use ng-blur that will be triggered only once you leave focus from input field.

Related

Calculate percentage based on input fields in Angularjs

I am using AngularJS. I have two input fields: total marks and marks obtained, as well as a 3rd field which is percentage. Now after entering total marks and marks obtained, I need to calculate the percentage automatically.
Here is my html :
<div class="form-group">
<label class="control-label">Total Marks</label>
<input type="text"
class="form-control input_field"
name="totalMarks"
data-ng-model="student.totalMarks" />
</div>
<div class="form-group">
<label class="control-label">Marks Obtained</label>
<input type="text"
class="form-control input_field"
name="marksObtained"
data-ng-model="student.marksObtained" />
</div>
<div class="form-group">
<label class="control-label">Percentage</label>
<input type="text"
class="form-control input_field"
value={{ ((student.marksObtained/student.totalMarks)*100).toFixed(2) }}
name="rank"
data-ng-model="student.rank" />
</div>
Can anyone tell me how to auto generate rank field with percentage as soon as I enter marks obtained. Currently, I am doing as shown above but it is not working.
Not sure that your calcul for percentage is exact, but this Plunkr is working with a watcher on student properties.
app.controller('MainCtrl', function($scope){
$scope.$watchGroup(['student.totalMarks', 'student.marksObtained'], function(){
if($scope.student.marksObtained !== undefined && $scope.student.totalMarks !== undefined)
$scope.student.rank = (($scope.student.marksObtained/$scope.student.totalMarks)*100).toFixed(2);
})
});
https://plnkr.co/edit/NsNge0mzrgjjUgBZ332p?p=preview
You can try assigning your calculation inline, like this:
<input ng-model="student.rank" ng-value="student.rank = ((student.marksObtained/student.totalMarks)*100).toFixed(2)">
Running Example on JsFiddle
It does not work because your input is linked to a ng-model.
There is two ways to solve this problem :
Setup a watcher on student.totalMars and student.marksObtained, and update student.rank with new value.
Do not link your percentage to student.rank and just set the value as you did.
You can check here for the second way.
In your controller, add the following function and variable :
function getPercentage() {
// dividing by zero is bad !!!
let ratio = $scope.student.marksObtained / ($scope.student.totalMarks || 1);
return (ratio * 100).toFixed(2);
}
$scope.student.percentage = getPercentage();
Then change the last <input /> of your view to :
<input type="text"
class="form-control input_field"
ng-value="student.percentage"
name="rank" />
If you want your input to have two-way data binding, use ng-model instead of ng-value.

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.

Angularjs ng-model with number input

I'm trying to retrieve an input[number] in my controller.
I found a solution which said to add a $scope.$watch on the input.
I tried it but now my number is "NaN".
<div class="offerText">
<label class="form-inline" ng-if="!admin">What will it cost ? <input type="number" class="form-control" ng-model="estimation"/></label>
<label class="form-inline">Comments <textarea rows="3" cols="50" class="form-control" ng-model="comments"></textarea></label>
</div>
And In my controller
$scope.$watch('estimation', function (val, old) {
$scope.estimation = parseFloat(val);
});
Thx for your help !
You don't need a watch. The problem here is that the input is inside an ng-if directive, which defines its own scope. The estimation is stored in the ng-if scope instead of being stored in the controller scope.
Rule or thumb: always have a dot in your ng-model.
In your controller, add
$scope.data = {};
And in the html, use
ng-model="data.estimation"
and
ng-model="data.comments"
Test if the new value is not undefined or null or something falsy, this happens because angular loops a few times through the watchs and the models, this process is call dirty checking
$scope.$watch('estimation', function (val, old) {
if (!val) {
return;
}
$scope.estimation = parseFloat(val);
});
When your value is "" or null you will always get parseFloat result as NaN
$scope.$watch('estimation', function (val, old) {
return !isNaN(val) ? parseFloat(val): 0; //considering default value is 0
});
Other possible way would get value on change, using ng-change directive.
<div class="offerText">
<label class="form-inline" ng-if="!admin">What will it cost ? <input type="number" class="form-control" ng-model="estimation" ng-change="changedEstimation(estimation)"/></label>
<label class="form-inline">Comments <textarea rows="3" cols="50" class="form-control" ng-model="comments"></textarea></label>
</div>
Controller
$scope.changedEstimation = function(val){
return !isNaN(val) ? parseFloat(val): 0;
}

Have AngularJs update {{binding}} as the user types in input[email]

Angular only updates the model from an input[email] after the user has entered a valid email address. How can I add a {{binding}} somewhere on the page that will update with the email value as the user types -- even before the user has typed in a valid email address?
Here's what I've tried so far:
<div ng-app>
<div ng-controller="MyCtrl">
<form name="MyForm" novalidate>
Name: <input type="text" name="name" ng-model="contact.name" /><br/>
Name as you type: {{contact.name}}<br/>
Email: <input type="email" name="email" ng-model="contact.email" /><br/>
Email as you type: {{contact.email}} (doesn't work)<br/>
Also doesn't work: {{$document.forms.MyForm.elements.email.value}}
</form>
</div>
</div>
Controller:
function MyCtrl($scope) {
$scope.contact = {};
}
(fiddle)
The name updates in real-time like I want, but the email doesn't.
I'd like to leave the email validation enabled. I just need some way to bind the un-validated input[email] text, so it updates as the user types.
Update 2014/7/8
I'd like to add an explicit requirement that the type="email" remains unchanged. I do not want to change the semantics of the markup to workaround a limitation of the framework. If need be, I'd rather pull in a complementary dependency (such as jQuery) to shim in the needed functionality.
I'm not opposed to handling validation in the controller — as suggested by rageandqq and charlietfl — if it could be done easily. Looking around though, it looks like it could be tricky (given my requirements).
That is how angularjs works. If you use <input type="email" /> angular is not going to bind your input till input will be valid in this case value must be a proper e-mail address.
please read more here : https://github.com/angular/angular.js/issues/1426
The workaround I've come up with so far is to use jQuery to listen for the input change and update an object on $scope that I've called formRaw. It works. Still, I'm hoping someone will come along and show me a better way.
The updated example:
<div ng-app>
<div ng-controller="MyCtrl">
<form name="MyForm" novalidate>
Name: <input type="text" name="name" ng-model="contact.name" /><br/>
Name as you type: {{contact.name}}<br/>
Email: <input type="email" name="email" ng-model="contact.email" /><br/>
Email Model: {{contact.email}}<br/>
Email Form: {{formRaw.email}}
{{q}}
</form>
</div>
</div>
And controller:
function MyCtrl($scope) {
$scope.contact = {};
$scope.formRaw = {};
$('input[type=email]').on('keyup change', function () {
var input = $(this);
$scope.formRaw[input.attr('name')] = input.val();
$scope.$digest(); // FIXME: there's got to be a better way
});
}
(fiddle)
The type="email" attribute on your E-mail input is what is causing the DOM binding to mess up.
Changing it to type="text" works allows your {{contact.email}} to display correctly.
Edited JSFiddle.

AngularJS: ngModel with ngSwitch, cannot watch?

I'm trying to make a very simple signup form, which is supposed to work in 2 steps.
I ask the user for his email in the first step
in the second step, I ask the user for the other details (step == 2).
The template is simple;
<div class="input-group layout-container" ng-switch on="step">
<!-- Login -->
<img src="images/yeoman.png">
<p>{{msg}}</p>
<form name="signup">
<input type="text" class="form-control login" placeholder="firstname" ng-model="firstname" ng-switch-when="2">
<input type="text" class="form-control login" placeholder="lastname"ng-model="lastname" ng-switch-when="2">
<input type="email" class="form-control login" placeholder="email" ng-model="email" ng-switch-when="1" required>
<span class="error" ng-show="signup.input.$error.required">Cannot be blank</span>
<span class="error" ng-show="signup.input.$error.email">Not a valid email</span>
<input type="password" class="form-control login" placeholder="password" ng-model="pass" ng-switch-when="2"> <br/><br/>
<div class="btn-container">
<button class="btn btn-primary" ng-click="next()">Next</button>
</div>
</form>
</div>
As you can see, there's some basic validation going on as well, and I assign the value of the email to a model I call email.
And the controller goes like this:
.controller('SignupCtrl', function ($scope) {
$scope.msg = "First, your email";
$scope.step = 1;
$scope.email = ''; // this still doesn't help
$scope.$watch('email', function (now, then, scope) {
console.log('email change', now, then);
});
$scope.next = function () {
$scope.step = Math.min($scope.step + 1, 2);
};
})
The problem is this: the $watch on 'email' doesn't ever trigger at all. Where's this going wrong?
I agree with #miqid, that ng-switch creates a new scope implicitly. All the value changes of <input> are actually happened in this isolated child scope. Its parent, where you are doing $watch, will NOT be notified. That's the reason why you cannot watch the variable changes. This is a common pitfall to angular users.
LT;DR simply change any ng-model="foo" to ng-model="$parent.foo" in your ng-switch block.
Here is a working jsfiddle (of just the part you have a problem with): demo
function ctrl($scope) {
$scope.items = ['one','two'];
$scope.selection = 'two';
$scope.data = {email:''};
$scope.$watch('data.email', function (now, then, scope) {
console.log('email change', now, then);
});
}
Note that it will only trigger the watch if the email is valid if the input is type="email".
Edit: updated for use with ng-switch.
Explanation: As noted by miqid, when you use primitives, child scopes have no access to parent scope variables, but when they're objects angular creates a reference in the child scopes.

Resources