Suppose you have 3 input text fields - f1, f2, f3. And want you want is to have f3 a "computed" field based on what's the values on f1 and f2. Simple enough, right? Not exactly.
If you do did something like this:
<div ng-app>
<div ng-controller="CTRL">
<input type="text" ng-model="f1" />
<input type="text" ng-model="f2" />
<input type="text" value="{{total()}}" />
<p>{{'Angular works!'}}</p>
</div>
</div>
And your Angular script is like this:
function CTRL ($scope) {
$scope.f1= 3;
$scope.f2= 4;
$scope.total = function() {return $scope.f1 + $scope.f2;};
}
You are in for a bad time. You will notice it will work at the start but when you change the value on either f1 or f2, the total field is showing a concatenated string and not a sum. DAFUQ! Peculiarity #1. The fix is actually pretty easy if you use a directive.
var app = angular.module('intDirective', []);
app.directive('integer', function(){
return {
require: 'ngModel',
link: function(scope, ele, attr, ctrl){
ctrl.$parsers.unshift(function(viewValue){
return parseInt(viewValue);
});
}
};
});
To use this is to add a ng-app="intDirective" property to the root div and the input tags should look like this:
<div ng-app='intDirective'>
<div ng-controller="CTRL">
<input type="text" ng-model="f1" integer/>
<input type="text" ng-model="f2" integer/>
<input type="text" value="{{total()}}" />
<p>{{'Angular works!'}}</p>
</div>
</div>
OK, its looking good but try typing in a character on either f1 or f2? Yes, another thing we have do. We have to check the value being typed it is not shit (sometimes called Validation).
var app = angular.module('intDirective', []);
var INTEGER_REGEXP = /^\-?\d*$/;
app.directive('integer', function(){
return {
require: 'ngModel',
link: function(scope, ele, attr, ctrl){
ctrl.$parsers.unshift(function(viewValue){
ctrl.$parsers.unshift(function(viewValue) {
if (INTEGER_REGEXP.test(viewValue)) {
// it is valid
ctrl.$setValidity('integer', true);
return parseInt(viewValue);
} else {
// it is invalid, return undefined (no model update)
ctrl.$setValidity('integer', false);
return undefined;
}
});
}
};
});
What's left is a simple $watch function to mimic Knockout computed fields (Peculiarity #2). Just add this snippet inside the controller.
$scope.$watch(function(){
return $scope.val1 + $scope.val2;
}, function(newValue, oldValue){
$scope.computed = newValue;
});
There.