I've searched my way, but can't figure this out. I made a directive manyToOneSelect (custom component) that loads items from the server, shows them to the user and lets the user pick one. That works well, but I cannot figure out how to prevent the form from being submitted if no item is picked by the user, i.e. how to invalidate the form.
Below is pretty much the directive:
angular.module('myApp.directives').
directive('manyToOneSelect', function(entityService) {
return {
restrict:'E',
templateUrl:'partials/control/n21select.html',
scope:{
entityName:'@',
entityField:'@',
bindVariable:'='
},
compile:function (tElement, tAttrs, transclude) {
return function (scope, element, attrs) {
var inner = element.children("#n21select");
scope.entities = [];
scope.$watch('entityName', function ($new, $old) {
entityService.getList(scope.entityName, function (data) {
scope.entities = data;
}, []);
}, true);
scope.lookup = function(uuid) {
for(var i in scope.entities) {
if(scope.entities[i].uuid == uuid) {
return scope.entities[i];
}}}}}}});
Here is the corresponding partial partials/control/n21select.html:
<select ng-hide="disable" ng-options="entity.uuid as entity[entityField] for entity in entities" ng-model="bindVariable" required></select>
<span ng-show="disable">{{lookup(bindVariable)[entityField]}}</span>
Here is how I use the directive:
<form ng-href="#/" ng-submit="save()">
<many-to-one-select entity-name="customer" entity-field="name"
bind-variable="entity.customerUuid"></many-to-one-select>
...
My problem seems lack of strategy, rather than "not entirely getting it to work", hence you don't see any attempt in the code I posted above. Let this be then a fairly open question: how to do it? :) Much appreciated already!
There's a few ways to do this.
Considering how you've already built out the directive, one way is to add a scope attribute for the form itself. something like:
scope: {
form: '='
}
Then you'd pass your form element in like so:
<form name="myForm" ng-submit="whatever()">
<my-directive-name form="myForm"></my-directive-name>
</form>
And in circumstance in your directive you wish to invalidate your form, you'd just call $setValidity on it:
link: function(scope, elem, attr) {
if(somethingIsWrong) scope.form.$setValidity('reason', false);
}
That's ONE way to do it, here's a BETTER way to do it if you can re-engineer your directive:
The other way, which is probably preferred, is to have your directive require ngModel. Then you'll have more grainular control over your validation, as ngModel's controller will be passed in and you can use that to invalidate both your form, and a singular field on your form:
app.directive('bettererWay', function() {
return {
require: 'ngModel',
restrict: 'E',
link: function(scope, elem, attr, ngModel) {
if(somethingIsBad()) ngModel.$setValidity('somethingIsBad', false);
}
};
});
And that's how you do it, in a nutshell. Hopefully that gets you started in the right direction.
EDIT: Weird issue with submission regardless of validity (in comments)
This is apparently an issue caused by Angular trying to adhere to the HTML specs.
From the comments in their code approx. line 214 here:
* To prevent double execution of the handler, use only one of ngSubmit or ngClick directives. This
* is because of the following form submission rules coming from the html spec:
*
* - If a form has only one input field then hitting enter in this field triggers form submit
* (`ngSubmit`)
* - if a form has has 2+ input fields and no buttons or input[type=submit] then hitting enter
* doesn't trigger submit
* - if a form has one or more input fields and one or more buttons or input[type=submit] then
* hitting enter in any of the input fields will trigger the click handler on the *first* button or
* input[type=submit] (`ngClick`) *and* a submit handler on the enclosing form (`ngSubmit`)
So, given the above, it might be a good idea to have your directive tied to an input element of type hidden on the page rather than being it's own element. If you have more than one element on the form, invalidity prevents submission just fine.