I have a commonly reused set of form inputs that are reused throughout my application, so I am trying to encapsulate them in a custom directive. I want to set an ngModel
on my directive and have that split up to be editable in several different inputs (some of them are directives themselves) within the main directive.
At the same time, I need the form validation results to be passed up the chain to a parent form so that I can display appropriate messages and styles.
What is the simplest and most idiomatic way to implement this?
These (simplified) templates should give you an example of what I'm going for...
OuterTemplate.html
<form name="outerForm">
<my-directive
ng-model="ctrl.myComplexModel"
name="myDirectiveInstance"
custom-required="ctrl.EnableValidateOne"
toggle-another-validation="ctrl.EnableValidateTwo">
</my-directive>
<div ng-messages="outerForm.myDirectiveInstance.$error">
<ng-message when="customRequired">This is required.</ng-message>
<ng-message when="anotherValidation">This is required.</ng-message>
<ng-message when="innerValidationOne">Something wrong with field 1.</ng-message>
<ng-message when="innerValidationTwo">Something wrong with field 2.</ng-message>
<ng-message when="innerValidationThree">Something wrong with field 3.</ng-message>
<!-- etc... -->
</div>
</form>
myDirectiveTemplate.html
<div ng-form="myDirectiveForm">
<div ng-class="{'has-error': myDirectiveForm.fieldOne.$invalid}">
<ui-select
ng-model="model.fieldOne"
name="fieldOne"
required>
</ui-select>
</div>
<div ng-class="{'has-error': myDirectiveForm.fieldTwo.$invalid}">
<input
type="number"
ng-model="model.fieldTwo"
name="fieldTwo"
ng-pattern="directiveCtrl.someRegEx"
ng-required="directiveCtrl.fieldTwoIsRequired">
</div>
<!-- etc... -->
</div>
At the moment, both myDirectiveForm
and myDirectiveInstance
are publishing themselves as properties of the outerForm
FormController
. I hoping to make this directive a black box, so the fact that myDirectiveForm
is attaching directly to outerForm
bothers me and seems to indicate that I'm doing something wrong.
Here's what my directive definition looks like right now.
myDirective.js
app.directive('myDirective', function() {
return {
restrict: 'E',
template: 'myDirectiveTemplate.html',
controller: 'MyDirectiveCtrl',
scope: {
model: '=ngModel',
customRequired: '=?',
toggleAnotherValidation: '=?'
},
require: 'ngModel',
link: function(scope, iElem, iAttrs, ngModelController) {
// Black-box the internal validators
// Custom validator to avoid conflicts with ngRequired
ngModelController.$validators.customRequired = function(modelValue, viewValue) {
if(!scope.customRequired)
return true;
// On first digest the field isn't registered on the form controller yet
if(angular.isUndefined(scope.myDirectiveForm.fieldOne))
return true;
return !scope.myDirectiveForm.fieldOne.$error.required;
};
ngModelController.$validators.anotherValidation = function(modelValue, viewValue) {
if(!scope.anotherValidation)
return true;
return scope.passesBusinessRule();
};
ngModelController.$validators.innerValidationOne = function(modelValue, viewValue) {
if(!scope.anotherValidation)
return true;
if(angular.isUndefined(scope.myDirectiveForm.fieldTwo))
return true;
return !scope.myDirectiveForm.fieldTwo.$error.pattern;
};
/* etc... */
// Deep-watching model so that validations will trigger on updates of properties
scope.$watch('model', function() {
ngModelController.$validate();
}, true);
}
};
});