Angularjs $viewValue.length for form validation - angularjs

I have a simple from to which I am trying to add validation. I am comparing the $viewValue in the form with the value of ng-min/mg-max to notify the user if he is trying to enter values that are over the max and under the min allowed. The issue is that if I use ng-if="myform.$viewValue.length > maxvalue" the error does not appear. However, the error DOES appear when I exclude .length() However, in this case it is checking every integer in of $viewValue string with maxvalue number, which is not intended. I understand that $viewValue.length would give me a number instead of a string so that I may compare a number with a number, but in this case the error never appears.
I even have a JSFiddle example that works https://jsfiddle.net/up2qxcxe/
But it does not in my app.
Here is my form:
<form name="modelParamsForm" novalidate>
<div class ="row">
<div class="form-group col-md-4" ng-repeat="modelParam in
modelParams" ng-class="{ 'has-error' :
modelParamsFieldForm.value.$invalid }">
<ng-form name="modelParamsFieldForm">
<label form-group>{{modelParam.label}}</label>
<input type="number" class="form-control input-sm"
ng-model="modelParam.value" name="value"
ng-min="modelParam.minvalue" ng-max="modelParam.maxvalue" >
<span ng-show="modelParamsFieldForm.value.$invalid"></span>
//MIN-MAX VALIDATOR
<div ng-if="modelParamsFieldForm.value.$viewValue.length >
modelParam.maxvalue">Max exceeded:{{modelParam.maxvalue}}
</div>
</ng-form>
</div>
</div>
My controller is nothing special:
$scope.modelParams = {};
$scope.loadModelParams = function(modelName) {
var req = {
method : 'GET',
url : urlPrefix + 'PB_OPTIMISER_GET_MODEL_PARAMS',
params: {modelName : modelName}
};
$scope.loading = true;
$http(req).then(function(response) {
$scope.loading = false;
$scope.modelParams = response.data;
$scope.modelParamsOld = angular.copy($scope.modelParams);
}, function() {
$scope.loading = false;
});
};
EDIT: In my JSON modelParam.maxvalue is a string. I think the problem was that I am comparing a myform.$viewValue.length with modelParam.maxvalue which is string type. I am not sure how to get around this...do I have to parse maxvalue?

You do not need $viewValue. Your ng-ifs are wrong. Change to
<input type="number" class="form-control input-sm col-md-5" ng-model="modelParam.value" name="test" min="modelParam.minvalue" max="modelParam.maxvalue">
<span ng-if="modelParam.value > modelParam.maxvalue">Too Many!!</span><br>
<span ng-if="modelParam.value < modelParam.minvalue">Too Few!!</span>
It should work fine. Here is your Plunker working the way I think you want.

The problem here is that the modelParam.maxvalue was coming from the database as a string, rather than integer. So when comparing ng-if="myform.$viewValue.length > modelParam.maxvalue" I was comparing an integer with a string, and that obviously did not produce the desired results. The solution was to go to the table and make maxvalue an integer.
Alternatively one can have a second variable for maxvalue in the controller that parses the string like so: $scope.mpMaxValue = parseInt($scope.modelParams.maxValue)
See this post for a longer discussion: AngularJS comparing $viewValue against ng-model or object param

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.

dynamic add input in ng-repeat causes last element to steal focus in safari

I'd like users to be able to enter multiple answers to a question, so I have an ng-repeat of text inputs that grows as the user adds more answers.
This is working fine in chrome, but in safari, the last element steals the focus as soon as it's added spreading a fast typing user's answer across several inputs.
In iOS safari it's even worse, as it's causing the browser to hang.
Here's the code for the form
<form class="form-horizontal sparse"
ng-controller="AssessmentController as assessment">
<fieldset>
<div class="form-group alt-uses-list" ng-repeat="response in answer.responses">
<label for="answer{{$index}}" class="col-xs-1 control-label">{{$index + 1}}.</label>
<div class="col-xs-10">
<input type="text" class="form-control"
id="response.uses{{$index}}"
autocomplete="off" tabIndex="{{$index + 1}}"
autofocus="{{$first}}" ng-model="response.value"
ng-change="assessment.addAnswerIfNeeded()">
</div>
</div>
</fieldset>
</form>
Here's the controller
function AssessmentController($scope, $log) {
$scope.answer = {};
$scope.answer.responses = [{id:0, value:""},{id:1, value:""}];
this.addAnswerIfNeeded = addAnswerIfNeeded;
// Add answer if the last two answers are non-empty
function addAnswerIfNeeded() {
var answers = $scope.answer.responses;
if ((answers[answers.length - 1].value != '') ||
(answers[answers.length - 2].value != '')) {
addAnswer();
}
}
// Adds a new answer if this is the last element
// isLast is needed to prevent non-lazy evaluation bugs
function addAnswer() {
$scope.answer.responses.push({
id:$scope.answer.responses.length, value: ""
});
};
}
I've created a jsfiddle.
The autofocus attribute is a Boolean type.
According to this link:
The value passed as the first parameter is converted to a boolean value, if necessary. If value is omitted or is 0, -0, null, false, NaN, undefined, or the empty string (""), the object has an initial value of false. All other values, including any object or the string "false", create an object with an initial value of true.
Do not confuse the primitive Boolean values true and false with the true and false values of the Boolean object
autofocus="{{$first}}" generates autofocus="false" on any item that is not the first item in the ng-repeat. As the link states
All other values, including any object or the string "false", create an object with an initial value of true.
If you can update to a angular JS version that includes the ng-attr directive you can update your code to be the following, I have updated you jsfiddle to show a working example
<form class="form-horizontal sparse" ng-controller="AssessmentController as assessment">
<fieldset>
<div class="form-group alt-uses-list" ng-repeat="response in answer.responses">
<label for="answer{{$index}}" class="col-xs-1 control-label">{{$index + 1}}.</label>
<div class="col-xs-10">
<input type="text" class="form-control" id="response.uses{{$index}}" autocomplete="off" tabIndex="{{$index + 1}}" ng-attr-autofocus="{{$first||undefined}}" ng-model="response.value" ng-change="assessment.addAnswerIfNeeded()">
</div>
</div>
</fieldset>
</form>
When the ng-attr-autofocus="{{$first||undefined}}" evaluates to false, the autofocus attribute gets the value of undefined and thus the autofocus gets removed from the dom.

Angularjs Input text appending number instead of adding it

When user type in number then click on "+", it will append the count instead of perform addition for the counter.
But it is working fine when performing "-" or "+" action without key in number in the input box.
<div ng-app="myApp" ng-controller ="myCtrl">
<span ng-click="count = count + 1">+</span>
<input type="text" ng-model="count" valid-number>
<span ng-click="count = count - 1" ng-show="count > 0">-</span>
</div>
fiddle : http://jsfiddle.net/shenglim/Lyugypqk/4/
Anybody can help out on this? Thanks !
simple example will work if uses ng-click="test()" and perform basic addition, if you need to retrieve from existing json which return string, Try to use parseInt() on the JSON object.
Try <input type="number">.
The data type for type="text" makes it a string and "1" + 1 = "11"
JSFiddle ~ http://jsfiddle.net/Lyugypqk/2/
Alternatively you can add two methods to increase and decrease the values into the controller and use them,
$scope.increment = function(){
$scope.count++;
};
$scope.decrement = function(){
$scope.count--;
};
<span ng-click="increment()">+</span>
<input type="text" ng-model="count" valid-number>
<span ng-click="decrement()" ng-show="count > 0">-</span>

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;
}

Don't record invalid values with ng-model

I really like how the ng-model attribute binds directly to my model and users get instant feedback on their changes. For my use case that's perfect. However, I don't want invalid values to be put into the model where they can throw a wrench into the calculations. I somehow want the model to only be updated if the value in the form control is valid. For invalid values, it's fine for the control value to change while the model value stays fixed.
If I change the source of angular (1.2rc) NgModelController's $setViewValue implementation:
this.$setViewValue = function(value) {
...
if (this.$modelValue !== value) {
this.$modelValue = value;
...
}
};
To this:
this.$setViewValue = function(value) {
...
if (this.$modelValue !== value && this.$valid) {
this.$modelValue = value;
...
}
};
It seems to do exactly what I want, however I don't know how to do this in a proper way. What's the right way to change this behavior? Or are my attempts doomed to failure for some reason?
Update: Added example.
For example look at http://jsfiddle.net/FJvgK/1/ HTML:
<div ng-controller="MyCtrl">
{{validNumber}}
<form>
<input
type="number"
ng-model="validNumber"
required
min="10"
max="20"
/>
</form>
</div>
And the JS:
var myApp = angular.module('myApp',[]);
function MyCtrl($scope) {
$scope.validNumber = 15;
}
The number shows properly for values between 10 and 20, but I want it so that if you suddenly type '8' into the box, or delete the second digit leaving '1' the last valid number still shows above. That is, the model always has a valid value, even if the control does not.
I believe the default behaviour of AnugularJS validators are not to update the model if the value passed is invalid. If you look at the developer guide and go through Custom Validation these samples also show that the model is not update or is cleared on invalid value provided in the UI
This is default behaviour, but, you can modify this using ngModelOptions directive
<input
type="number"
ng-model="validNumber"
required
min="10"
max="20"
ng-model-options="{ allowInvalid: true }"
/>
Documentation: https://docs.angularjs.org/api/ng/directive/ngModelOptions See the section 'Model updates and validation'
As Chandermani said, it is the default behavior, here is a example that shows it in action :
<form name="myform">
<input type="text" name="myinput" ng-model="myvalue" ng-minlength="4" required>
</form>
Is the input valid ? {{ myform.myinput.$valid }} <br />
Input's value : {{ myvalue }}
{{ myvalue }} doesn't show anything until you write at least 4 characters in the input.
Best Regards.
EDIT
If you need a default value, I guess you could break down your value into 2 values, using a computed value :
var validNumber = 15;
$scope.validNumber = function () {
if ($scope.myform.myNumber.$valid) return $scope.myNumber;
else return validNumber;
};
I set up an example here : http://jsfiddle.net/7vtew/1/

Resources