Michael Lane Michael Lane - 3 months ago 22
AngularJS Question

$observe not firing on changes and not interpolating

TLDR: Why does angular's ngMinlength receive $observe updates with interpolated values, but my custom validation directive does not?

Link to plnkr

I am working on a custom validation directive in Angular 1.3 and have noticed something that seems inconsistent. The directive in angular seems to get interpolated updates from attr.$observe, but the directive I create does not behave the same way.

I can use $watch to fix it, or bind an interpolated value, but is inconsistent with the existing validation directives. What's the difference, and how can I make my custom directive work similarly to the built in validation directives?

Angular's directive

var minlengthDirective = function() {
return {
restrict: 'A',
require: '?ngModel',
link: function(scope, elm, attr, ctrl) {
if (!ctrl) return;

var minlength = 0;
attr.$observe('minlength', function(value) {
minlength = int(value) || 0;
ctrl.$validate();
});
ctrl.$validators.minlength = function(modelValue, viewValue) {
return ctrl.$isEmpty(viewValue) || viewValue.length >= minlength;
};
}
};
};


My directive

function observeMinLength($log){
return {
restrict: 'A',
require: '?ngModel',
link: function (scope, elm, attr, ctrl) {
if (!ctrl) return;

var min;

//Problem 1: observered value is not interpolated
//Problem 2: observe is only fired one time
attr.$observe('observeMinlength', function (value) {
$log.debug('observed value: ' + value);
min = parseInt(value, 10) || 0;
ctrl.$validate();
});

ctrl.$validators.mymin = function (modelValue, viewValue) {
var len = 0;
if (viewValue){
len = viewValue.length;
}
return ctrl.$isEmpty(viewValue) || viewValue.length >= min;
};
}
};
}

Answer

Notice that the ngMinlength directive is $observing the "minlength" attribute, not the "ngMinlength" attribute. I believe the issue is that Angular's input directive is setting the minlength attribute based on the interpolated value... so while the ngMinlength attribute doesn't change as its value changes, the minlength attribute does which is why it is observable. In your directive, the attribute observeMinLength doesn't change, but the value of the scope property you pass in does.

If you look in the angular source code: https://github.com/angular/angular.js/blob/13b7bf0bb5262400a06de6419312fe3010f79cb2/src/ng/directive/attrs.js#L379 you can see that angular is watching the scope variable and setting the attribute variable for all attributes in the ALIASED_ATTR collection. That collection is defined as

var ALIASED_ATTR = {
  'ngMinlength': 'minlength',
  'ngMaxlength': 'maxlength',
  'ngMin': 'min',
  'ngMax': 'max',
  'ngPattern': 'pattern'
};

in https://github.com/angular/angular.js/blob/2e0e77ee80236a841085a599c800bd2c9695475e/src/jqLite.js#L575

So in short, Angular has special handling for its own attribute based validation directives. So you will need to watch your scope property rather than observe the attribute.

Comments