AngularJS: Controller - Directive bidirectional communication

I'm trying to create some custom validations for custom input components.

The directive of the custom input would act as a black box, so I can add the validations that I want from the controller.

To do that I add an attribute for the directive, called 'validations', p.e.:

<custom-input validations="checkEmail() checkIsNotUsed()" type="text" placeholder="Email"></custom-input>

With this example, I can define my validations from the controller as I want, with the idea that when I want to submit from the controller, it will check if this component is validated for 'checkEmail()' and 'checkIsNotUsed()'. To do that, I would need some bidirectional communication between the controller and the inner directive, p.e. using the email:

  1. Controller wants to validate the component, so it will call the inner directive to get the internal input element.
  2. The directive returns the Input to the controller.
  3. Controller can check the input based on the validations linked to him.

I've tried different approaches, but I'm not able to get the final goal.

Here is the example I've been working with.

EDIT

Solved: http://plnkr.co/edit/DMUVLifuWeGEuXOVfRwE?p=preview

I would slightly question whether you need the directive as a complete black box. Firstly, usually you would need some validation messages or some information shown to the user on the different cases of invalid data, so just a true/false return value of any validator isn't enough. Secondly, you're also re-creating a lot of what Angular already gives you with ngForm, ngModel, ngModelController and the input directive, especially the $setValidity function of ngModelController

I would put each validator in its own directive that requires ngModel: one for email, and one for password for example. You can still pass options in from the overall controller however, but each directive is tailored to the information it needs. For example, a validationEmail directive can accept an array of existing emails used:

<input validation-email validation-email-used="usedEmails" ng-model="data.email" type="text" placeholder="Email" name="email" ng-required />

Where usedEmails is an array of emails. The directive could be written as:

app.directive('validationEmail', function() {
  return {
    require: 'ngModel',
    scope: {
      validationEmailUsed:'='
    },
    link: function(scope, element, attributes, ngModelController) {
      scope.$watch(function() {
         return ngModelController.$viewValue
       }, function(email) {
         // Test the email and call
         // ngModelController.$setValidity(...)
         // to set the validity of the email
      });
    }
  };
});

You can see this in action in a modified version of your Plunker

Edit: If you want to integrate with server-side validation, once all client-side validation has passed from the directives, you can do it from function passed to ngSubmit on the form:

<form name="myForm" ng-submit="submit()">

Which can be coded up using the fact the form exposes itself on the scope, and all its ngModel controllers (for the same of example, using a $timeout, rather than calling $http or a service),

$scope.submit = function() {
 $timeout(function() {
   $scope.myForm.username.$setValidity('available',false);
   $scope.myForm.email.$setValidity('emailFree',false);
  },500);
}

An issue with this is that you then might want to mark the input as valid after the user has changed it. You could create a validOnChange directive, to be used as:

<input ng-model="data.username" type="text" placeholder="Username" name="username" valid-on-change="available" ng-required />

and coded up as:

app.directive('validOnChange', function() {
  return {
    require: 'ngModel',
    scope: '',
    link: function(scope, element, attributes, ngModelController) {
      scope.$watch(function() {
        return ngModelController.$viewValue
      }, function() {
        ngModelController.$setValidity(attributes.validOnChange,true);
      });
    }
  }
});

You can see this in this Plunker.

Another general benefit of working with ngForm, and ngModelController, is that it adds a lot of classes to the form and element depending on the error/valid state of the inputs, and exposes error state on the scope, so you can show/hide error messages easily using ngIf, for example.