I can't figure out what's happening in the following example. I just trying to create my own required
validation in my own directive where I have an array and I want to make it required (it's a simplification of what I want to do but enough to show the point)
Fiddler: http://jsfiddle.net/gsubiran/p3zxkqwe/3/
angular.module('myApp', [])
.directive('myDirective', function($timeout) {
return {
restrict: 'EA',
require: 'ngModel',
controller: 'myDirectiveController',
controllerAs: 'D_MD',
link: function(scope, element, attrs, ngModel) {
ngModel.$validators.required = function(modelValue) {
var result = false;
if (modelValue && modelValue.length > 0)
result = true;
return result;
};
},
bindToController: {
ngModel: '='
},
template: '(<span>ArrayLength:{{D_MD.ngModel.length}}</span>)<br /><input type=button value="add (inside directive)" ng-click=D_MD.AddElem() /><br /><input value="clear (inside directive)" type=button ng-click=D_MD.Clear() />'
}; }) .controller('myDirectiveController', [function() {
var CTX = this;
//debugger;
//CTX.ngModel = "pipo";
CTX.clearModel = function() {
CTX.ngModel = [];
};
CTX.AddElem = function() {
CTX.ngModel.push({
Name: 'obj100',
Value: 100
});
};
CTX.Clear = function() {
CTX.ngModel = [];
}; }]) .controller('MainCtrl', function($scope) {
var CTX = this;
CTX.patito = 'donde esta el patito';
CTX.arrayElements = [];
CTX.setElements = function() {
CTX.arrayElements = [{
Name: 'obj0',
Value: 0
}, {
Name: 'obj1',
Value: 1
}, {
Name: 'obj2',
Value: 2
}];
};
CTX.clearElements = function() {
CTX.arrayElements = [];
}; })
When I hit the add (outside directive)
button, the required works fine,
but when I hit the add (inside directive)
button I still getting the required error in the form (the form is defined outside directive).
But the more confusing thing for me is the following:
When I hit the clear (inside directive)
button after hitting add (outside directive)
button to make the required error go out, in this case the form is updating and the validation error is showing.
Why $validations.required is not firing inside the directive when I add new element to array but yes when I clear it?
Any ideas?
******* UPDATE *******
It seems to be related with array.push
if I change array.push with the assignation of new array with wanted elements inside it works ok.
Still the question why it is happening.
As workaround I changed in the directive the AddElem
function in this way:
CTX.AddElem = function() {
CTX.ngModel = CTX.ngModel.concat({
Name: 'obj100',
Value: 100
});
};
The ngModel
you use here is a JS object. Angular has a reference to that object in its $modelValue
and $viewValue
(because angular basically does $viewValue = $modelValue
). The $modelValue
is the actual value of the ngModel
, which, if you change it, will change the $viewValue
after having run $validators
.
To know if your ngModel
has Changed, angular compares the ngModel.$viewValue
with the ngModel.$modelValue
. Here, you are doing a push()
to the $viewValue
which is updating the $modelValue
at the same time because they are just references of each other. Therefore, when comparing them, they have the same value ! Which is why angular does not run your $validator
.
Since ng-model does not do a deep watch, $render() is only invoked if the values of $modelValue and $viewValue are actually different from their previous values. If $modelValue or $viewValue are objects (rather than a string or number) then $render() will not be invoked if you only change a property on the objects.
If I over-simplify angular code, this snippet explains it:
var myArray = [];
var ngModel = {
$viewValue: myArray,
$modelValue: myArray,
$validate: function () { console.log('validators updated'); }, // log when validators are updated
}
function $apply() { // the function that is called on the scope
if (ngModel.$viewValue !== ngModel.$modelValue) {
ngModel.$viewValue = ngModel.$modelValue;
ngModel.$validate(); // this will trigger your validator
} else {
console.log('value not changed'); // the new value is no different than before, do not call $validate
}
}
// your push is like doing :
ngModel.$viewValue.push(12);
$apply(); // will output 'value not changed', because we changed the view value as well as the model value
// whereas your should do:
var newArray = [];
// create a copy of the array (you can use angular.copy)
for (var i = 0; i < myArray.length; i++) {
newArray.push(myArray[i]);
}
ngModel.$viewValue.push(12);
ngModel.$viewValue = newArray; // here we clearly update the $viewValue without changing the model value
$apply(); // will output 'validators updated'
Of course you are not forced to do an array copy. Instead, you can force the update of your ngModel. This is done by calling ngModel.$validate();
One way of doing it would be to add a forceUpdate()
function in your scope
, and call it from the controller after you do a push();
Example: http://jsfiddle.net/L7Lxkq1f/