I have 2 input fields that need to get validated after both fields have been entered. It's a credit card expiration date, so theres a month
and year
. I'm using a third-party service to actually do the validation.
Therefore, I setup 3 directives: exp
, expMonth
, and expYear
.
I use a $watch
to validate the user input - however I would like to show an error if the validation is false. When I attempt to do ng-class
with the exampleForm.$error.expiry
, I get Error: [$rootScope:infdig] 10 $digest() iterations reached.
Here is a demo http://plnkr.co/edit/SSiSfLB8hEEb4mrdgaoO?p=preview
view.html
<form name='exampleForm' novalidate>
<div class="form-group" ng-class="{ 'is-invalid': exampleForm.$error.expiry }">
<div class="expiration-wrapper" exp>
<input type='text' ng-model='data.year' name='year' exp-month />
<input type='text' ng-model='data.month' name='month' exp-year />
</div>
</div>
exp.directive.js
angular.module('example')
.directive('exp', ['ThirdPartyValidationService',
function(ThirdPartyValidationService) {
return {
restrict: 'A',
require: 'exp',
link: function(scope, element, attrs, ctrl) {
ctrl.watch();
},
controller: function($scope, $element, ThirdPartyValidationService) {
var self = this;
var parentForm = $element.inheritedData('$formController');
var ngModel = {
year: {},
month: {}
};
var setValidity = function(exp) {
var expMonth = exp.month;
var expYear = exp.year;
var valid = ThirdPartyValidationService.validateExpiry(expMonth, expYear);
parentForm.$setValidity('expiry', valid, $element);
};
self.setMonth(monthCtrl) {
ngModel.month = monthCtrl;
};
self.setYear(yearCtrl) {
ngModel.year = yearCtrl;
};
self.watch = function() {
$scope.$watch(function() {
return {
month: ngModel.month.$modelValue,
year: ngModel.year.$modelValue
};
}, setValidity, true);
};
}
};
}]);
expMonth.directive.js
angular.module('example')
.directive('expMonth', [
function() {
return {
restrict: 'A',
require: ['ngModel', '^?exp'],
compile: function(element, attributes) {
return function(scope, element, attributes, controllers) {
var formCtrl = controllers[0];
var expMonthCtrl = controllers[1];
expMonthCtrl.setMonth(formCtrl);
};
};
};
}]);
expYear.directive.js
angular.module('example')
.directive('expYear', [
function() {
return {
restrict: 'A',
require: ['ngModel', '^?exp'],
compile: function(element, attributes) {
return function(scope, element, attributes, controllers) {
var formCtrl = controllers[0];
var expYearCtrl = controllers[1];
expYearCtrl.setYear(formCtrl);
};
};
};
}]);
The problem is that you are setting the validity error to a jquery selection with the line:
parentForm.$setValidity('expiry', valid, $element);
Then you are watching that jQuery element via ng-class
when you have:
ng-class="{ 'is-invalid' : exampleForm.$error.expiry }"
in your markup. This causes strange exceptions to be thrown when angular tries to deep copy this value.
Instead, change it to use the .length
property of the jQuery object instead. If non-zero (truthy), the is-invalid
class will be set correctly, otherwise it won't be.
ng-class="{ 'is-invalid' : exampleForm.$error.expiry.length }"
You could also do ...expiry.length > 0
if that makes more sense to you and future developers who have to look at your code.
I forked your Plunkr so it works with this change.