0

In a custom validation directive I ensure that the entered value does not conflict with others in the database. If it does, I would like to tell the user not only that there's a conflict, but also the name of the item it is conflicting with.

Storing these details in the scope would probably work, but doesn't seem right to me at all. Is there a better way?

Directive:

angular.module('myApp')
  .directive('conflictcheck', function (myServer) {
    return {
      require: 'ngModel',
      link: function (scope, elm, attrs, ctrl) {
        ctrl.$parsers.unshift(function (viewValue) {
          var conflict = myServer.getConflict(viewValue);
          if (!conflict) {
            ctrl.$setValidity('conflictcheck', true);
            return viewValue;
          } else {
            ctrl.$setValidity('conflictcheck', false);

            // pass additional info regarding the conflict here

            return undefined;
          }
        });
      }
    };
  });

View:

<form name="myform" action="#">
  <input ng-model="foo" conflictcheck />
  <div ng-if="myform.foo.$error.conflictcheck">
     Error: Foo conflicts with XXXXXXXX!
  </div>
</form>
hoeni
  • 3,031
  • 30
  • 45

1 Answers1

1

Validation errors are handled by FormController so it's reasonable to keep all validation-related data inside the form object itself. This could be implemented by attaching arbitrary data to myform instance:

app.factory('myServer', function(){
  return {
    getConflict: function(viewValue){
      var comparedAgainst = 'existing@email.com';
      if(viewValue === comparedAgainst){
        return comparedAgainst;
      }
    }
  }
});

app
  .directive('conflictcheck', function (myServer) {
    return {
      require: ['ngModel', '^form'],
      link: function (scope, elm, attrs, ctrls) {
        var ngModelCtrl = ctrls[0];
        var ngFormCtrl = ctrls[1];

        ngModelCtrl.$parsers.unshift(function (viewValue) {
          // getConflict returns conflicting value
          var conflict = myServer.getConflict(viewValue);

          if (!conflict) {
            ngModelCtrl.$setValidity('conflictcheck', true);
            return viewValue;
          } else {
            ngModelCtrl.$setValidity('conflictcheck', false);
            // We attach validation-specific data to arbitrary data property
            ngFormCtrl.foo.data = {
              conflictcheck: conflict
            };

            return undefined;
          }
        });
      }
    };
  });
<form name="myform" novalidate>
  Email: 
  <input ng-model="foo" name="foo" conflictcheck />
  <div ng-show="myform.foo.$error.conflictcheck">
   Error: Foo conflicts with {{myform.foo.data.conflictcheck}}!
  </div>
</form>

Plunker

Stewie
  • 60,366
  • 20
  • 146
  • 113
  • It works like a charm that way, thank you so much! I use elm[0].id now instead of the hardcoded element name to allow for multiple checks in one form: ngFormCtrl[elm[0].id].data = {... Still I don't fully understand what I'm doing here ;-) What exactly is requested by the '^form' require and assigned to ngFormCtrl? Is there a place in the docs where I can find about that? – hoeni Nov 29 '13 at 08:08
  • `require` allows array of directives to be specified as a dependency to your directive. In this case, your directive will throw error if ngModel is not available on the same element on which you are applying your own directive, and it will also throw an error if it doesn't find a `form` directive in any of parent elements. http://docs.angularjs.org/api/ng.$compile – Stewie Nov 29 '13 at 08:45