Here is the code refactored a bit (Note: you need to be using the latest Angular for some of this). After rereading your question I am not sure what exactly you are having trouble with (whether it is how to use required in the directive definition object or how to use ngRequired attribute or something else). Note that with the code below you do not need $scope:
angular.module('myApp', []);
angular.module('myApp').directive('validator', validator);
function validator (){
return {
restrict: 'E',
require: {
ngModelCtrl: 'ngModel'
},
replace: true,
templateUrl: 'view.html',
scope: {}, //this controls the kind of scope. Only use {} if you want an isolated scope.
controllerAs: 'ctrl',
bindToController: {
rows: '=',
onSelected: '&?', //passsed selected row outside component
typedText: '&?', //text typed into input passed outside so developer can create a custom filter, overriding the auto
textFiltered: '@?', //text return from the custom filter
ngRequired: "=?" //default false, when set to true the component needs to validate that something was selected on blur. The selection is not put into the input element all the time so it can't validate based on whether or not something is in the input element itself. I need to validate inside the controller where I can see if 'this.ngModel' (selectedRow - not passed through scope) is undefined or not.
},
controller: 'validatorController'
}
}
//usually do this in a new file
angular.module('myApp').controller('validatorController', validatorController);
validatorController.$inject = ['$element'];
function validatorController($element){
var ctrl = this;
//controller methods
ctrl.validate = validate;
ctrl.$onInit = $onInit; //angular will execute this after all conrollers have been initialized, only safe to use bound values (through bindToController) in the $onInit function.
function $onInit() {
if(ctrl.ngRequired)
ctrl.ngModelCtrl.$validators.myCustomRequiredValidator = validate;
}
//don't worry about setting the invalid class etc. Angular will do that for you if one if the functions on its $validators object fails
function validate (modelValue, viewValue){
//validate the input element, if invalid add the class ng-invalid to the .form-group in the template
//return true or false depending on if row was selected from dropdown
return rowWasSelected !== undefined
}
}
Here are a couple of snippets from Angular's docs on $compile:
If the require property is an object and bindToController is truthy,
then the required controllers are bound to the controller using the
keys of the require property. This binding occurs after all the
controllers have been constructed but before $onInit is called.
and
Deprecation warning: although bindings for non-ES6 class controllers
are currently bound to this before the controller constructor is
called, this use is now deprecated. Please place initialization code
that relies upon bindings inside a $onInit method on the controller,
instead.
Again, make sure you are using the latest version of Angular or the above won't work. I can't remember exactly which part (I feel like it might be getting the require object keys auto-bound to the controller object), but I have definitely run into a nasty bug where the above wasn't working and I was using 1.4.6.
Second Edit: Just want to clear up a few things:
1) the .ng-invalid class will be applied to any input in an angular validated form that is invalid. For example, if there is a required attribute on an input and the input is empty, then the input will have an ng-invalid class. Additionally, it will have a class .ng-invalid-required. Every validation rule on the input gets its own ng-invalid class. You say you want to add a red border to an input after it has been blurred for the first time. The standard way to do this is to have a css rule like this:
.ng-invalid.ng-touched {
border: 1px #f00 solid;
}
If you inspect a validated input you will see all kinds of angular classes. One of them is .ng-touched. A touched element is one that has been blurred at least once. If you wanted to ensure that validation is only applied on blur you could use ng-model-options directive.
2) $formatters are used to format a model value. Angular has two way data binding. That means that angular is $watching a model value and view value. If one of them changes angular executes a workflow to update the other one. The workflows are as follows:
view value changes -> $parsers -> $validators -> update model value
model value changes -> $formatters -> update view value
The result of the work flow is populated into the other value. This means that if you want to change model value before showing it in the view (maybe you want to format a date) then you could do it in the $formatter. Then, you could do the opposite operation in a $parser as it travels back to the model. Of course, you should be cognizant of what is happening in the $parsers when you write your $validators because it is the parsed view value that gets validated before getting sent to the model.
3) Per the quote I added from the angular docs, it is clear that you should not use any logic that contains a value that has been bound to the controller by bindToController outside of $onInit. This includes ngModelCtrl. Note that you could place the logic in another function as long as you are sure that the other function will execute AFTER $onInit.
4) There are two things to consider here: Which control is having the error show up and where are you triggering the validation from. It sounds like you want to trigger it from the dropdown's workflow (i.e. after it has been blurred once). So, I suggest adding a validator to the dropdown. Now, you say you want to validate the input and not the dropdown. So, you can use $setValidity inside the validator. To ensure that the dropdown is always "valid" you can just return true from the validator. You say you want to only validate after blur. There are two ways to do that (off the top of my head). One is to use the ng-model-options that I mentioned above, the other is to test if the dropdown has been $touched in the validator. Here is some code using the second method:
function validate (modelValue, viewValue) {
var inputField = ctrl.formCtrl.inputName, ddField = ctrl.formCtrl.ddName;
inputField.$setValidity('validationName', ddField.$touched && rowSelectedCondition);
return true;
}
You see, I am testing to see if the dropdown has been $touched (i.e. blurred) before I set the validity. There is a fundemental difference between these two approaches. Using ng-model-options basically defers the whole update workflow until blur. This means your model value will only get updated to match the view value after the input has been blurred. The second way (with $touched) will validate every time the viewValue changes but will only render the input invalid after the first blur.
The 'validationName' argument will just specify the class that is added if the input is invalid so in this case it will add two classes .ng-invalid (added to any invalid control) and .ng-invalid-validation-name.
In order to get access to the formCtrl you need to add another property to your require object (formCtrl: '^form')