Changing `ng-required` calls `ng-change` - javascript

I have a strange behavior in my app using AngularJS 1.5.8:
plunker (https://plnkr.co/edit/zaHVJeK8hdxk2gaOL9Pf?p=preview) and video(http://recordit.co/eszvfdfC9S)
step 1. At the beginning changing ng-required doesn't call ng-change function
step 2. After making changes in input AND removing them (input is empty) ng-required DOES call ng-change function
expected behavior?
step 2. After making changes in input AND removing them (input is empty) ng-required SHOULD NOT call ng-change function. As it was at the beginning, and as it is when input has some value
Please let me know if it's a bug or not. If not then why changing ng-required calls ng-change NOT always or even at all?
ANSWER IS FOUND-------------------------------------------------------------------------------------------------------
NgModelController has two properties: $viewValue (value entered by
user) and $modelValue (value bound to your model). When the value
entered by the user fails validation, NgModelController sets the model
to undefined.
In AngularJS 1.3, they added the ng-model-options directive. It lets you
configure how/when the model gets updated. You can use the
allowInvalid option to prevent your model from being set to undefined:
ng-model-options="{allowInvalid: true}"

You should add
ng-model-options="{allowInvalid: true}"
So the final result will be
<input type="text"
ng-change="$ctrl.onChange()"
ng-required="$ctrl.isRequired"
ng-model-options="{allowInvalid: true}"
ng-model="$ctrl.val"
/>

This is happening because the ng-required is changing the attached modal value to undefined from blank when the required is set to false, due to this ng-change is fired since the modal changes.
Check in the plunker i have console.log the value of input and you can see the change of modal clearly.
angular.
module('myApp', []).
component('greetUser', {
templateUrl: 'tmpl.html',
controller: function GreetUserController() {
this.output='';
this.isRequired = false;
console.log(this.val);
this.onChange = function() {
console.log(this.val);
this.output+='Changed\n';
}
}
});
plunker : https://plnkr.co/edit/6IeIjIDahcmBIU4KSASJ?p=preview
Now the question arises that why not the on load/ first time the change event is not firing up that is because we are using this object rather then $scope.
Here
'this' vs $scope in AngularJS controllers
is a very good example which explains why until we manually enter the value in the input at least once the change event is not firing up.
in short this is happening because ng-change/ng-model works with scope variables. Once you manually enter value in the input element, the model binding happens with the scope, and the ng-change event start firing up.

I think you misunderstanding.
ng-required and ng-change is different things. doing different purpose, and not calling each other.
ng-change is calling your function no matter what it's empty or not. It's call your method by it self, regarding to changes happening in the input.
ng-required is just checking value if it's empty or not, If it is empty, mark it as invalid.
In order to get what you want, you have to check the validity inside the onChange function.
this.onChange = function() {
if( !$scope.$ctrl.form.$valid ) return;
this.output+='Changed\n';
}

I think this is the reason. put an alert inside ng-change like this.
this.onChange = function() {
alert(this.val);
this.output+='Changed\n';
}
When you empty the box after completing it the value change between two values:
undefined when is required
'' when is not required
So By changing the radio box you call the ng-change,
At the beginning you have
However when you have not started to type in the text box , radio box does not change the input value , because ng-change is for input. In the beginning we have undefined --> undefined so nothing changed. then when you empty the input you have '' ---> undefined.
Actually if you put this in your controller you get call ng-change at the beginning too.
this.val ='';
So if you replace your controller with this , you see ng-change is called even at the beginning.
angular.
module('myApp', []).
component('greetUser', {
templateUrl: 'tmpl.html',
controller: function GreetUserController() {
this.output='';
this.isRequired = false;
this.val ='';
this.onChange = function() {
this.output+='Changed\n';
}
}
});

Related

$scope.$watch is not triggered when ng-model is undefined?

Problem:
I am trying to use $scope.$watch method over a ng-model used in a number input with strict attributes like, max, min, maxlength and step.
When the value inserted in the input exceeds any of those attributes, the ng-model retrieves undefined.
So, eventually, what happens is: $scope.$watch is triggered every time we change value in the input. Once the value is undefined, it will only be triggered again when the inserted value is again valid, in other words, following attribute rules (max, min, maxlength and step).
E.g.
user input: -2,4
-> $scope.$watch is triggered, and outputs, newValue as undefined.
user adds a new digit: -2,44
-> $scope.$watch is not triggered anymore, this way.
main.js
$scope.$watch("user.input.base.sphere", function(newValue, oldValue) {
console.log(newValue);
}
**index.html
<input
ng-cloak
type="number"
ng-class="user.settings.input.sphere.class"
autocomplete="off"
required
name="sphere"
id="in-sphere"
title="Sphere"
step="{{user.filter.sphere.step}}"
min="{{user.filter.sphere.min}}"
max="{{user.filter.sphere.max}}"
maxlength="{{user.filter.sphere.maxlength}}"
placeholder="{{user.filter.sphere.placeholder}}"
ng-model="user.input.base.sphere"
select-on-click
sphere>
Question: How can I still let $scope.$watch to be triggered even over a undefined ng-model
You can attach the following function to the ng-keyup directive. This function will get a reference to your form control and read the value that the user has typed in even though the value in the model has not yet been updated.
$scope.changeHandler = function (a) {
var element = angular.element(document.querySelector('#in-sphere'))[0];
console.log(element.value);
}
Attach it to your form control with this:
ng-keyup='changeHandler()'
Here is a working plunker

Why does AngularJS filter only run once?

Consider the following example:
angular.module('app', []).controller('TestController', function($scope) {
$scope.getText = function() {
console.log('getting text');
return 'text';
};
}).filter('text', function() {
return function() {
console.log('text filter');
return 'text';
};
});
<script src="https://ajax.googleapis.com/ajax/libs/angularjs/1.6.2/angular.min.js"></script>
<div ng-app="app" ng-controller="TestController">
<p>{{getText()}}</p>
<p>{{'' | text}}</p>
</div>
Notice that the getText() function runs twice whereas the filter only runs once. I assume the getText() function runs twice to make sure the model is now stable. Why not the same behavior for the filter?
The documentation is pretty clear on this subject:
In templates, filters are only executed when their inputs have changed. This is more performant than executing a filter on each $digest as is the case with expressions.
Here's the source.
Cosmin is exactly right - and here's a demo to prove it (which, coincidentally, will cause a stack overflow at some point) - when getText() is called, it assigns a new value to the input of the text filter, which causes it to re-evaluate, which causes another digest cycle, which causes the filter to reevaluate... which eventually causes something like a stack overflow.
EDIT I removed a testing portion that was causing the overflow - this will only have the filter evaluate twice, since getText is called only twice.
angular.module('app', []).controller('TestController', function($scope) {
$scope.foo = 'bar';
$scope.getText = function() {
console.log('getting text');
$scope.foo += 'a';
return 'text';
};
}).filter('text', function() {
return function() {
console.log('text filter');
return 'text';
};
});
<script src="https://ajax.googleapis.com/ajax/libs/angularjs/1.6.2/angular.min.js"></script>
<div ng-app="app" ng-controller="TestController">
<p>{{getText()}}</p>
<p>{{foo | text}}</p>
</div>
When an expression is used in a template, then AngularJS first evaluates the material/text inside braces (Interpolation) and then converts the value/output to string and then insert this string value into the HTML element/attribute.
From AngularJS Docs:-
In templates, filters are only executed when their inputs have changed. This is more performant than executing a filter on each $digest as is the case with expressions.
There are two exceptions to this rule:
In general, this applies only to filters that take primitive values
as inputs. Filters that receive Objects as input are executed on each
$digest, as it would be too costly to track if the inputs have
changed.
Filters that are marked as $stateful are also executed on each $digest. See Stateful filters for more information. Note that no AngularJS core filters are $stateful.
As per documentation,
Filters are only executed when their inputs have changed. This is more performant than executing a filter on each $digest as is the case with expressions.
There are two exceptions to this rule:
In general, this applies only to filters that take primitive values as inputs. Filters that receive Objects as input are executed on each $digest, as it would be too costly to track if the inputs have changed.
Filters that are marked as $stateful are also executed on each $digest. See Stateful filters for more information. Note that no AngularJS core filters are $stateful.
Since you have a primitive value which doesn't change, the filter doesn't have to execute again. Change the empty string '' to an object literal {} and see what happens ;)
Both bindings are being checked twice, but you can only see one being checked twice. In the case of the filter, the input is ''. Angular is only checking the INPUT (source) to the filter when dirty checking and it checks it twice and compares results. So it doesn't call the filter twice.
For the curly brace binding, the RESULT of the expression is checked twice, and so you can see your function being called twice.
But if you make the input to the filter a function like this you'll see it's called twice just like your curly brace binding and the filter is still called only once.
Changing the filter input to a function shows that input is checked twice and filter is called just once:
{{getText() | text}}

Watch ng-model whilst using minlength?

I'm trying to watch the value of ng-model whilst also using the minlength validation. The problem is the model value remains empty/undefined until the validation criteria is met.
HTML
<input ng-model="xyz" minlength="8" />
JS
$scope.$watch('xyz', function(val) {
// Will either be undefined or a
// string bigger than or equal
// to 8 characters.
console.log(val);
});
I know I could just substring the element's value, but this code is implemented in a directive which uses $compile, so ideally I'd prefer to watch the model value.
Any thoughts on how to resolve this?
https://docs.angularjs.org/api/ng/directive/ngModelOptions
allowInvalid: boolean value which indicates that the model can be set with values that did not validate correctly instead of the default behavior of setting the model to undefined.

AngularJS : Detect form changes

I'm developing an angular app, where I have to check the form changes, and perform some operation depending upon the changes. I know, this can be achieved using $scope.watch but that would cost me performance.
What I'm trying to achieve
I've a pre-populated form and I will take the form data as a reference using angular.copy() and compare with original data.
I had also tried $parsers to check, but failed. So, is there any way to accomplish this?
You can use ng-change.
From the docs - "Evaluate the given expression when the user changes the input. The expression is evaluated immediately, unlike the JavaScript onchange event which only triggers at the end of a change (usually, when the user leaves the form element or presses the return key)."
Difference between ng-change and $watch
Did you try with ng-change ? It call a function when value of ng-model change
<input type="search" ng-change="changeValue()" ng-model="test" name="test"/>
If you want to know wich field call the function you can add the name as string parameter
<input type="search" ng-change="changeValue('field1')" ng-model="field1" name="field1"/>
In the controller don't forget to put the function changeValue in the scope
$scope.changeValue = function(fieldname){
switch (fieldname){
case 'field1' : .... break;
case 'field2' : .... break;
case 'field3' : .... break;
...
}
}
When you compare the copied object against the original form data, and you want to check for changes, you can use angular.equals.
angular.equals(formObj, formObjCopy)
This will do a deep compare of the object and its nested sub-properties, and return true if the object hasn't changed.

How to force validation to run on model when other model changes?

I have an input field with a number of $validators registered on it that updates the model.
Some of these validators does comparisons to other values on the scope (which are also updated with input fields).
How do I force AngularJS to run these validations again when the other values are changed on which it is dependant?
I've tried finding anything relating to this in the documentation and also created a $watch on the dependant field and just set the model value to itself (hoping it would force a revalidation) but no luck on either counts.
If you're using Angularjs 1.3+ you can use the $validate method.
Lets say your input "A" is the one which depends on the others inputs, lets call them "B"s.
You can add a function to each of the B's $viewChangeListeners which will just call the A's $validate method. This will have the following effect; each time you modify one of the B input, your A inputs $validators will run.
I know that this was answered a while ago, but I had a similar issue and I managed to cobble a suitable solution together from a stack of other answers and a bit of trial and error. I figure someone else might look for something similar one day...
Here's a method which (as far as I can tell ) ties directly in with the validation system. This particular example creates a match validation rule, which compares two models and validates if their value is identical.
<script type="text/javascript">
angular.module( "YourModule", [] )
.directive( "match", function() {
return {
require: 'ngModel',
restrict: 'A',
link: function( $scope, $elem, $attrs, $ctrl ) {
// targetModel is the name of the model you want to
// compare against.
var targetModel = $attrs.match;
// Add the 'match' validation method
$ctrl.$validators.match = function( modelValue, viewValue ) {
valid = $scope.$eval( targetModel ) == viewValue;
$ctrl.$setValidity( 'match', valid );
return valid ? viewValue : undefined;
};
// When the target model's value changes, cause this model
// to revalidate
$scope.$watch( targetModel, function() {
$ctrl.$validate();
} );
}
};
} );
Then use like this (obviously including a form, ng-app and ng-controller):
<input type="password" ng-model="password" />
<input type="password" ng-model="confirmation" match="password" />
The general idea is that when the match directive is processed, an additional function (match) is added to the $validators on the $ctrl object, which is where other validators (required field, min length, ...) seem to live. This sets up the validation on the field, but that rule is only processed when the field with the match directive is updated.
To combat this, a watch is then set up on the target model. When the value of the target model is updated, it will run the $validate() method on the original control, allowing validation to occur in either of the two fields.

Categories