2

I'm looking for a way to override ng-submit so that it performs some functions before evaluating/running the expression it contains. For example, I would like to do the following.

1) Set all fields dirty (or perhaps touched) so that all fields are validated even if the user has skipped over them.

2) Check that all fields validate. If not then don't continue.

3) If any fields are invalid then scroll the first invalid field and focus it.

I have found a few directives that do some of this, some create new element directives but none actually override/extend ngSubmit so I'm wondering if this is possible?

jonhobbs
  • 26,684
  • 35
  • 115
  • 170

2 Answers2

4

First, an element need not be "touched" for validation to work (that's about point #1). For example, this would invalidate the input, given $scope.test = "abcd"; and:

<input ng-model="test" ng-maxlength="3">

Second, #2 is easily achieved with form.$valid:

<form name="form1" ng-submit="form1.$valid && onSubmit()">
  ...
</form>

If pre-submit logic is more complicated then this, it could/should be done in the controller, for example, in the onSubmit() function.

But, if your pre-submit logic is View-related (as opposed to ViewModel-related) - and scrolling is View-related - then you could create another ngSubmit directive with higher priority and prevent default submit event handling:

.directive("ngSubmit", function() {
  return {
    require: "?form",
    priority: 10,
    link: {
      pre: function(scope, element, attrs, form) {
        element.on("submit", function(event) {        
          if (form && !form.$valid) {
            event.stopImmediatePropagation();
            event.preventDefault();

            // do whatever you need to scroll here
          }
        })
      }
    }
  }
});

Demo

EDIT:

Using pre-link is important here due to order of link function executions. The order of execution is:

1. pre-link of parent or higher priority directive
2. pre-link of child or lower priority directive
3. post-link of child or lower priority directive
4. post-link of parent or higher priority directive

So, the use of higher priority and pre-link ensures that this directive registers element.on("submit", ...) before the built-in ngSubmit does it, so it can have a first go at event handling.

New Dev
  • 48,427
  • 12
  • 87
  • 129
  • Awe neat. Setting the priority and creating a new ng-submit is not something I thought of. – Cory Silva May 24 '15 at 17:09
  • I think setting the priority is the key to this, thanks. Just one remaining question though. Why does does the "element.on" go in the pre function here? Normally I just return a function in the link which I believe is the equivelant of using post: function(){} and I know that in some circumstances you have to use pre: funciton(){} but I've never needed to and wondered why it needs to be that way here? – jonhobbs May 24 '15 at 17:15
  • @jonhobbs, because of the need to register `element.on("submit", ...` *before* the built-in directive does it. So, first, the priority has to be higher, but then you also need `pre`-link because the order of execution is top-to-bottom (and higher priority to lower), whereas `post`-link executes in the opposite order – New Dev May 24 '15 at 17:24
1

This code should get you started as it addresses criterias number 1, 2 and gives you a hook for number 3.

As for scrolling to the invalid fields, I have not tried/needed that yet but sounds interesting. I guess you can get real bored and create an entire "form wrapper directive", though seems like overkill..

I would just use a service method that can be called in my controller. Are you thinking of just scrolling to the first invalid field and focusing it?

Template

<!-- Form Template -->
<form name="form" novalidate ng-submit="vm.submit(form.$valid, vm.data)">
    <input type="text"
           name="blah"
           ng-model="vm.data.blah"
           ng-model-options="{debounce: {'default': 300, blur: 0}}"
           required
           formnovalidate/>
     <div ng-messages="form.blah.$error"
          ng-messages-include="messages.html"
          ng-if="form.$submitted || form.blah.$touched">
     </div>
    <button type="submit">Submit</button>
</form>

<!-- messages.html -->
<div ng-message="required">This field is required</div>

Controller

vm.data = {};
vm.submit = function(isValid, data) {
    if (!isValid) { 
        //Scroll to bad field
        return; 
    }
    // Do form submission via service
};
Cory Silva
  • 2,761
  • 17
  • 23
  • Thanks Cory, but my forms already look like this, using ng-if and checking for validity in the submit handler, that's why I'm attempting to override the ngSubmit so that it automatically sets everything to dirty and checks for validity. – jonhobbs May 24 '15 at 16:54
  • What do you want to accomplish by setting everything to dirty? I ask because a form field can be both pristine and invalid. – Cory Silva May 24 '15 at 17:00
  • my ng-messages are going to be set to only show when the field is dirty so setting them all to dirty when you submit te form will show all the validation messages. – jonhobbs May 24 '15 at 17:11
  • Looking closely at my code you will also see the OR case on the ng-messages that acheives the same thing – Cory Silva May 24 '15 at 17:13
  • Yes I see that and I have similar code at the moment but it seems a lot of typing to do this every time when ng-messages should automatically only show when the element is dirty or when the form has been submitted. I don't know why this isn't the default behaviour in Angular so I'm going to have to write another directive for this but that's probably another question that needs to be asked separately. – jonhobbs May 24 '15 at 17:28