Listen for form submit event in directive

时光总嘲笑我的痴心妄想 提交于 2020-01-01 09:27:26

问题


I want to listen for form submitting in a directive. Say I have a directive like this:

app.directive('myDirective', function () {
    return {
        restrict: 'A',
        require: '^form',
        scope: {
            smth: '='
        },
        link: function (scope, el, attrs, formCtrl) {
            scope.$watch(function(){
                return formCtrl.$submitted;
            },function(currentValue){
                console.log('submitted');
            });
        }
    }
});

With the above method I can watch for first submit, but not the rest. I tried to do something like this:

scope.$watch(function () {
    return formCtrl.$submitted;
}, function (currentValue) {
    if (currentValue) {
        console.log('submitted');
        formCtrl.$setPristine(); // Watch this line!
    }
});

But then the problem is, if I use the directive in a form more than once, it works only for the first usage. What I want to know is if there is something like formCtrl.onsubmit(...) or any workaround to get the same functionality. Thanks in advance for any help...


回答1:


Instead of watching the $submitted property, you can create a directive that has the same name as the form directive which is attached with an event handler for form submit that broadcasts an angular event that you can listen in your myDirective directive. You don't have to worry about overriding the angular implementation of the form directive, it will simply append your behavior not overwrite the built-in implementation.

DEMO

Note: You can also choose not to append functionality to the form directive and instead choose another directive name, just make sure to attach that directive name as an attribute in the form tag to trigger the event.

Javascript

.directive('form', function() {

  return {
    restrict: 'E',
    link: function(scope, elem) {
      elem.on('submit', function() {
         scope.$broadcast('form:submit');
      });
    }
  };

})

.directive('myDirective', function() {
  return {
    require: '^form',
    link: function(scope, elem, attr, form) {
      scope.$on('form:submit', function() {
        form.$setPristine();
      });
    }
  };
});

Update

In light of the question raised in the comment below:

what's the most efficient way to check if the element that has "my-directive" attribute has "my-form" (if I name "form" directive to "myForm") attribute in it's parent form? So I can either use "myDirective" with or without "myForm" (and behave accordingly of course)

There are several ways to do it:

  1. Use the .data() method in your myForm directive during the compile phase, and access it in the link function in your myDirective using the .inheritedData() method if the data assigned in the form directive exists.

Note that I passed the form controller within the broadcast in the myForm directive. This ensures that you receive the parent form controller which is the from the form element. There are certain use cases wherein you would use the myDirective inside a nested form via ng-form, so instead of setting form.$setPristine() to the form element form controller you'd be setting the ngForm form controller.

DEMO

  .directive('myForm', function() {

    return {
      require: 'form',
      compile: function(tElem, tAttr) {

        tElem.data('augmented', true);

        return function(scope, elem, attr, form) {
          elem.on('submit', function() {
             scope.$broadcast('form:submit', form);
          });
        }
      }
    };

  })

  .directive('myDirective', function() {
    return {
      link: function(scope, elem, attr) {

        if(!elem.inheritedData('augmented')) {
          return;
        }

        scope.$on('form:submit', function(event, form) {
          console.log('submit');
          form.$setPristine();
        });
      }
    };
  });
  1. Another way which is probably something that is highly optimized for this specific use case. To create a controller in the myForm directive which stores form event handlers to be iterated when a form event is triggered. Instead of using the $broadcast angular event which is actually slower than the implementation below because it traverses each scope from the form element down to the last scope chain. The myForm controller below creates its own mechanism for storing the event handlers. As implemented in #1, using the .data() - inheritedData() is slow when the myDirective is buried deep and nested from a lot of elements, since it traverses the DOM upwards until it finds that specific data. Using the implementation below, you can check if the required ?^myForm controller exists in the parent, notice the ? it represents an optional requirement. Additionally, setting scope to true in the myForm directive allows you to have the directive reusable, e.g. have multiple myForm directives inside a page..

DEMO

  .directive('myForm', function() {

    return {
      require: ['form', 'myForm'],
      scope: true,

      controller: function() {

        this.eventHandlers = {
          submit: [],
          change: []
        };

        this.on = function(event, handler) {
          if(this.eventHandlers[event]) {
            this.eventHandlers[event].push(handler);
          }
        };

      },

      link: function(scope, elem, attr, ctrls) {
        var form = ctrls[0],
            myForm = ctrls[1];


        angular.forEach(myForm.eventHandlers, function(handlers, event) {
          elem.on(event, function(eventObject) {
            angular.forEach(handlers, function(handler) {
              handler(eventObject, form);
            });
          });
        });

      }

    };

  })

  .directive('myDirective', function() {
    return {
      require: '?^myForm',
      link: function(scope, elem, attr, myForm) {

        if(!myForm) {
          return;
        }

        myForm.on('submit', function(event, form) {
          console.log('submit');
          form.$setPristine();
        });
      }
    };
  });



回答2:


You can use ng-submit with a broadcast or something similar, but maybe first give $setUntouched() a try, or perhaps manually set $submitted back to false after you are done with the current submission.




回答3:


https://docs.angularjs.org/api/ng/directive/ngSubmit is probably what you're looking for.




回答4:


This post is probably dead but to build on the above, I found the form directive was not broadcasting to the other directives properly so I included everything in one directive.

Here is a simple functions that generates an alert based on form.$error if the form is invalid:-

// automated handling of form submit errors
myApp.directive('form', [ function() {
    return {
        restrict: 'E',
        require: '^form',
        link: function (scope, elem, attr, form) {
            elem.on('submit', function () {
                if(form.$invalid){
                    console.log('form.$error: ', form.$error);

                    Object.keys(form.$error).forEach(error => {
                        form.$error[error].forEach(elem => {
                            console.log('error elem is: ', elem);
                            alert(error + ' for ' + elem.$name + ' is invalid! Current: ' + elem.$modelValue);
                        })
                    })
                }
                form.$setPristine();
            });
        }
    };
}])


来源:https://stackoverflow.com/questions/26383507/listen-for-form-submit-event-in-directive

易学教程内所有资源均来自网络或用户发布的内容,如有违反法律规定的内容欢迎反馈
该文章没有解决你所遇到的问题?点击提问,说说你的问题,让更多的人一起探讨吧!