14

I have a situation where a user needs to enter input into text areas created by ng-repeat. If the user enters a value that has already been entered both the new and existing values should be validated as false. If one of the values (existing or new) gets changed, the validation should be updated correspondingly.

I have tried quite a variety of options, currently this is what comes close, but is still not 100%.

HTML:

<body ng-app="ap" ng-controller="con">
<table>
    <tr>
        <td>name</td>
    </tr>
    <tr ng-repeat="person in persons">
        <td>
            <ng-form name="personForm">
            <div ng-class="{ 'has-error' : 
                personForm.personName.$invalid }">
                <input type='text'
                name="personName"
                ng-class="empty"
                ng-model="person.name"
                ng-change="verifyDuplicate(this, person)"/>
                </div>
             </ng-form> 
        </td>
    </tr>
</table> 

JavaScript:

var app = angular.module("ap",[]);

    app.controller("con",function($scope){

    $scope.persons = [
        {name: 'a'},
        {name: 'b'},
        {name: 'c'}
    ];

    $scope.empty = "normal";

    $scope.verifyDuplicate = function(domScope, object){
        for(var i = 0; i < $scope.persons.length; i++) {
            if($scope.persons[i].name === object.name && $scope.persons[i] !== object) {
                domScope.personForm.personName.$setValidity('duplicate',false);
            }
            else {
                domScope.personForm.personName.$setValidity('duplicate',true);
            }
        }
    };
});

Any help on this would be appreciated.

Here is a fiddle Fiddle of code

1 Answer 1

15

I think it might because the verifyDuplicate method keeps setting and re-setting the validity of the same model, so the validity of the model triggering method invocation will be based on the last comparison result in the loop.

One way to solve this is to let the verifyDuplicate method work on the persons collection as a whole, no matter which model change triggers the method invocation, in this example the $setValidity method is not used, instead, a isDuplicate property on the model is set to indicate duplication.

HTML:

<ng-form name="personForm">
     <div ng-class="{ 'has-error' :
            personForm.personName.$invalid }">
            <input type='number'
            name="personName"
            ng-class="empty"
            ng-model="person.name"
            ng-change="verifyDuplicate()"/>
     </div>
 </ng-form>
<div class='error'
        ng-if='person.isDuplicate'>
        Duplicate.
</div>

JavaScript:

$scope.verifyDuplicate = function() {
        var sorted, i;
        sorted = $scope.persons.concat().sort(function (a, b) {
            if (a.name > b.name) return 1;
            if (a.name < b.name) return -1;
            return 0;
        });
        for(i = 0; i < $scope.persons.length; i++) {
            sorted[i].isDuplicate = ((sorted[i-1] && sorted[i-1].name == sorted[i].name) || (sorted[i+1] && sorted[i+1].name == sorted[i].name));
        }
    };

JSFiddler: http://jsfiddle.net/luislee818/pkhxkozp/4/

If we insist using $setValidity, I can think of connecting individual model to its form with "ng-init" directive, however this looks cumbersome and there might be better ways if we go with this approach.

HTML:

<ng-form name="personForm">
     <div ng-class="{ 'has-error' :
            personForm.personName.$invalid }">
            <input type='number'
            name="personName"
            ng-init="person.form = personForm"
            ng-class="empty"
            ng-model="person.name"
            ng-change="verifyDuplicate()"/>
     </div>
 </ng-form>
<div class='error'
        ng-show=
        'personForm.personName.$error.duplicate'>
        Duplicate.
</div>

JavaScript:

$scope.verifyDuplicate = function() {
        var sorted, i, isDuplicate;
        sorted = $scope.persons.concat().sort(function (a, b) {
            if (a.name > b.name) return 1;
            if (a.name < b.name) return -1;
            return 0;
        });
        for(i = 0; i < $scope.persons.length; i++) {
            isDuplicate = ((sorted[i-1] && sorted[i-1].name == sorted[i].name) || (sorted[i+1] && sorted[i+1].name == sorted[i].name));
            sorted[i].form.personName.$setValidity('duplicate',!isDuplicate);
        }
    };

JSFiddle: http://jsfiddle.net/luislee818/nzd87f1s/1/

Sign up to request clarification or add additional context in comments.

5 Comments

Great, I will give it a go as soon as I find a gap, hope it works in the real world problem I want to apply it to.
@dapeng-li This worked for me, but not good for unit testing. can this be converted to custom validation directive?
@SajidAli I'm not sure if this is suitable for custom validation directives, my understanding is that they are directives to be applied to HTML elements which collect input from user (like input, select, textarea). If we introduce a directive like that, it has to know the whole collection thus making it couple with not only the element to validate but also the collection containing it. If your concern is unit testing, maybe we can extract the verifyDuplicate function into a service, like this? jsfiddle.net/luislee818/pkhxkozp/7
@DapengLi Thank you for clearing out and providing jsfiddle. great help
The above solution works for me :)...But I need to display only one error msg at the time...Please let me know the solution...Thanks in advance

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.