Recently I had to make a form where the one of the underlying data columns has a unique constraint on it. The data isn’t very sensitive and doesn’t change very often, but I wanted to provide functionality to validate client side first, before actually submitting the form to the server. In this specific use case, I have to get the list of the strings in the specific column I want, then compare the entered text to the list.
My first approach was to simply do the comparison every time the input changes, using ng-change and $setValidity on the input. By doing this, you can then provide feedback using the $error object and check for the provided error definition. In the following example, that is “alreadyExists”. Documentation for $setValidity can be found here.
vm.keyChanged = function () { if(vm.model.key != null) vm.model.key = vm.model.key.toUpperCase(); $scope.mf.modalForm.key.$setValidity("alreadyExists", vm.keys.indexOf(vm.model.key) < 0); };
<input maxlength="2" required ng-change="mf.keyChanged()" /> <small class="error" ng-show="mf.modalForm.key.$error.alreadyExists"> This key already exists </small>
While leveraging ng-change and $setValidity does technically work, I figured it would be better to put that functionality into a reusable directive. This means I can add an attribute to an input, provide a list, and it will handle the validation on that input.
app.directive('notInList', function() { return { require: 'ngModel', scope: { notInList: '=' }, link: function(scope, element, attrs, control) { var list; scope.$watchCollection('notInList', function (newList, oldList) { list = newList; }); if(scope.notInList !== undefined){ control.$validators.isInList = function(modelValue, viewValue) { if (control.$isEmpty(modelValue)) // if empty, correct value { return true; } if (list.indexOf(modelValue) != -1) // Is already in list value { return false; } return true; // correct value }; } } }; });
Using this directive, you pass in an array of strings to compare to, and the $error object gets “isInList” set if the input matches one of the elements of the array. In the following example, directive “not-in-list” is added to myInput, and “testList” is an array passed in to that directive.
Your (case sensitive) input: <input type="text" name="myInput" ng-model="myInput" not-in-list="testList" /> <span style="color:red;" ng-show="myForm.myInput.$error.isInList"> This is already in the list </span>
Below is a plunker of the not-in-list directive in action. There is also a second directive called not-in-list-ignore-case, which provides the same functionality, but ignores case. Note that the testList is defined in the controller as [‘test’, ‘list’, ‘testing’, ‘invalid’]. This means that if you enter any of those strings into the input, it will display the isInList error message.
Thanks for a great component!
When I had just:
if(scope.notInList !== undefined){
I got a console error of: TypeError: Cannot read property ‘indexOf’ of undefined
When I changed it to:
if(scope.notInList !== undefined){
list = scope.notInList;
It worked.