The main things that are needed are:
Require ngModelController
to be able to call its methods and get/set its values. Specifically...
Replace the call element.val(y)
with
ngModelController.$setViewValue(y);
ngModelController.$render();
I think the I should admit, I'm not entirely sure on the inner workings of ngModelController
to understand why this is necessary.
Optional, but getting the existing value in the view by element.val()
can be instead done by:
ngModelController.$viewValue;
which is at least a more consistent with the way of setting the view value.
Again optional, but listening to the input
event makes the interface a bit nicer, as it seems to fire a bit before the keyup
event, so you don't get a flash of the unprocessed input.
Adding to the $parsers
array to process the input seems to stop any ngChange
callbacks being fired for the un-processed version of the input.
ngModelController.$parsers.push(function(val) {
// Return the processed value
})
Putting all this together as a custom directive:
app.directive('cleanInput', function() {
return {
require: 'ngModel',
link: function(scope, element, attrs, ngModelController) {
function clean(x) {
return x && x.toUpperCase().replace(/[^A-Z\d]/g, '');
}
ngModelController.$parsers.push(function(val) {
return clean(val);
})
element.on('input', function() {
var x = ngModelController.$viewValue;
var y = clean(x);
var start = this.selectionStart;
var end = this.selectionEnd + y.length - x.length;
ngModelController.$setViewValue(y);
ngModelController.$render();
this.setSelectionRange(start, end);
});
}
}
});
which can be used as:
<input type="text" clean-input ng-model="name">
or if you would like an ngChange
function:
<input type="text" clean-input ng-model="name ng-change="onChange()">
and seen in action at http://plnkr.co/edit/FymZ8QEKwj2xXTmaExrH?p=preview
Edit: add the part about $parsers
array. I should admit, it was @Engineer's answer that made me think of it.