2

I'm creating an angular directive which attaches to an angular form and modifies it's submit behaviour. The idea is to check validity and do a few things if the form isn't valid (show errors, scroll page etc). I have found some code to help with this but I can't seem to find how to re-create the ng-submit and make the form actually post if the form is valid. I have my own version of the ng-submit attribute which works like this - six4-submit="myController.myfunction()" so I guess it's a case of evaluating the angular expression inside the success part of my directive?

Here is the full code so far with a comment where the code needs to go.

.directive('six4Form', function($log) {
  return {
    restrict: 'A',
    scope: {
      six4FormSubmit: '@',
    },
    require: 'form',
    compile: function(element, attrs) {
      // Auto set novalidate
      if (!attrs.novalidate) {
        element.attr('novalidate', '');
        attrs.novalidate = true;
      }

      return {
        // In the pre-compilation section so it gets run before 
        // any other submit functions.
        pre: function(scope, element, attrs, formCtrl) {
          // Submit function
          element.bind('submit', function(event) {
            if (formCtrl && !formCtrl.$valid) {
              event.preventDefault();
              event.stopPropagation();
              event.stopImmediatePropagation();
            }
            // Form IS valid
            else {

              // Code here should evaluate contents if attrs.six4FormSubmit and run it

            }
          });
        }
      }
    }
  };
});

EDIT: Let me re-phrase the question as it's causing some confusion...

If I were to write ng-submit="mySubmit()" then the ngSubmit directive must be doing something to evaluate the expression it contains and find the mySubmit function on the scope and run it. I simply want to emulate that behaviour in my directive. Unfortunately I can't find the ngSubmit code in the Angular github repo.

EDIT 2: Getting there

Looking at the code for ng-submit it appears that something like this should work

var callback = $parse(attrs.six4FormSubmit);

scope.$apply(function() {
    callback(scope, {$event:event})
});

Unfortunately it doesn't and it's hard to debug as it produces no errors, it just doesn't run the callback function whereas ng-submit does.

jonhobbs
  • 26,684
  • 35
  • 115
  • 170
  • 1
    In my opinion this is not a good approach as ngSubmit is a directive it self. and not a functionality of ngForm. I recommend that you encapsulate the logic on a service and bind it to the ng-submit as you would do normally. – Raulucco May 25 '15 at 11:44
  • You should have asked in comments to your original question. You don't need to do anything - this does not overwrite the built-in `ngSubmit` (hence the need for `.stopImmediatePropagation`). The answer is: do nothing – New Dev May 25 '15 at 12:12
  • Hi New Dev, I actually wanted to use my own "six4-form-submit" attribute instead of ng-submit. Partly because I was going to make two separate directives six4-form and six4-submit and partly because I may want to change the behaviour in other ways in the future. So the question really is how to replicate ng-submit. I've searched the angular github repo for ngSubmit and can find the documentation for it but no code, it's very strange. – jonhobbs May 25 '15 at 12:26
  • Also, I posted it as a new question because my approach has changed, in my original question I was trying to overwrite ngSubmit, now I'm trying to do it all in a new directive that attaches to the angular form and handles more than just submission, so my original question about how to over-ride ngSubmit (which you kindly answered with a neat solution using priority and pre-link function) is no longer relevant. I'm trying to build a JSfiddle to make it easier to ask any future questions about it. – jonhobbs May 25 '15 at 12:30
  • @raulucco, the OP's intent (judging from his [original question](http://stackoverflow.com/questions/30425605/is-it-possible-to-override-ng-submit/30426044#30426044)) is to add View-only and app-wide functionality, that's why I suggested this approach. – New Dev May 25 '15 at 12:31
  • That's correct New Dev, a very succinct way of explaining why I'm trying to do this. – jonhobbs May 25 '15 at 12:37
  • It sounds as over engineering as you can validate elements with the default directives of angular for this purpose or create you custom validation directive to validate an input. and the messages get displayed by ngMessages module or the old way, ngShow directive. Then if you want to scroll to an error message. I suggest to create a directive that listen to the digest cycle on the current scope and base on that scrolls to the first visible error message. This way is you stick to one responsibility (scroll to the error message) – Raulucco May 25 '15 at 12:53
  • Sorry @raulucco, I don't agree, Angular requires you to write some pretty verbose view markup sometimes and in my opinion doesn't have very sensible defaults for form behaviours. This could all be done with separate directives but I'm trying to save myself from having to repeat myself in my views all the time. Many others have tried to do the same but none are exactly what I need. The best one I found was ng-fab-form https://github.com/johannesjo/ng-fab-form – jonhobbs May 25 '15 at 13:10
  • @jonhobbs I just tried to help because i think that the better way is to create small directives with precise behavior if you need to extend this behavior in my opinion is best to compose it with other components. For example in your application you should not have two forms that look the same so you won't have to duplicate error messages. But if you have inputs that repeat in other forms maybe you can encapsulate the whole input in a directive so you avoid writing twice the same validation. Seems to me that angular takes care of validation already. – Raulucco May 25 '15 at 13:32
  • On this point I agree with @Raulucco - I would advise against creating a monolithic directive and, instead, componentize where makes sense. On a larger question - the only reason to replicate `ngSubmit` is if you intend not to use it. But why? - Use it, in fact, and add your own directive (for example, `scrollOnInvalid`) to do pre-validation behavior (exactly how you implemented it) – New Dev May 25 '15 at 14:12
  • Btw, here's the [src for `ngSubmit` directive](https://github.com/angular/angular.js/blob/master/src/ng/directive/ngEventDirs.js#L49) (and other events - it's the same functionality) – New Dev May 25 '15 at 14:14
  • Thanks, that source helped but as you can see from my edit I'm not quite there yet. I may go back to using ng-submit but I think I owe it to this question to keep trying to solve it for a while. As for the question of whether one directive is right here I do understand the single responsibility principle and I stick to it when I think it's relevant, but when you know that all your forms need to do the same thing then adding multiple directives to every form or input in a large site doesn't seem very DRY to me. Plus, This way I get to add new functionality without changing all my views. – jonhobbs May 25 '15 at 15:13
  • @jonhobbs, fine, but why can't you use your directive (for scrolling and whatnot) in addition to `ngSubmit`? And if not that, why can't, in fact, create another `ngSubmit` directive and augment its behavior with additional attributes (if needed). I don't understand the need to replicate `ngSubmit` without using `ngSubmit` - but if you want, just copy the source code from `ngSubmit` in full and rename it – New Dev May 25 '15 at 15:22
  • I probably will end up using ng-submit but I hate getting stuck on a problem and not solving it. Unfortunately I'm at the limit of my Angular knowledge and I've tried unsuccessfully to copy the angular code in it's entirety. In the Angular codebase they are returning a function called ngEventHandler in the compile section whereas I'm inside the element.bind inside my "pre" section. That's where I need to put my code to run the function and it simply doesn't work, even when I rename everything to work with my naming conventions. – jonhobbs May 25 '15 at 15:54
  • You wouldn't need a `pre`-link function or `stopImmediatePropagation` - it was only needed to catch the event before the built-in directive got to it... If you were to fork the code, you could just augment the actual event handler with whatever functionality you needed. But I think, otherwise, your question is not very accurately defined... so, I wouldn't lose sleep over it – New Dev May 25 '15 at 16:01
  • I'm not sure how I could have defined the question any better than to write out the full working code and put a comment in which said "// Code here should evaluate contents if attrs.six4FormSubmit and run it". My question hasn't changed. I still want to use the code above and I still need to fill in the blanks. I didn't mean to get into a protracted discussion about whether my approach was correct or not and I know that people are only trying to help but I still don't have a solution. My attempts to create a jsFiddle have failed so far because I can't even get ngMessages working in jsFiddle! – jonhobbs May 25 '15 at 16:13
  • Let us [continue this discussion in chat](http://chat.stackoverflow.com/rooms/78707/discussion-between-new-dev-and-jonhobbs). – New Dev May 25 '15 at 16:16

1 Answers1

1

Perhaps I'm missing the greater point, but if I understand you, you want to add some app-wide behavior to all forms. You have a number of approaches:

1) Copy the ngSubmit code and create your own ngSubmit-like directive (not recommended, since I don't see an added value to forking the code just to have your own duplicate) - demo

2) Keep ngSubmit and add your own additional functionality (e.g. scrollToInvalid) via attributes

<form name="form1" ng-submit="onSubmit()" scroll-to-invalid>
  ...
</form>
.directive("ngSubmit", function(){
   return {
     // ...
     link: function(scope, element, attrs, formCtrl){
        if (formCtrl && formCtrl.$invalid){

          // ... similar to what you have in the question, plus

          if (attrs.scrollToInvalid){
             // implement scrolling
          }
          if (attrs.somethingElse){
             // do something else
          }
        }
     }
   }
})

This keeps the functionality of ngSubmit since you are using that directive.

3) Create your own directive if it could apply to a form even if you don't have ngSubmit. You could conditionally (by checking for ng-submit attribute) apply additional logic (like prevent submission on invalid inputs).

<form name="form1" ng-submit="onSubmit()" super-form>
   ...
   <div ng-form="innerFormWithSubmit" super-form>
   </div>
</form>
.directive("superForm", function(){
   function prelink(scope, element, attrs, formCtrl){
     if (attrs.ngSubmit){
       element.bind('submit', function(){
         // ... 
       })
     }
   }
   return {
     require: "form",
     link: {
       pre: prelink
     }
   }
}
New Dev
  • 48,427
  • 12
  • 87
  • 129