Detect the form submit event within a directive

In my directive, I am trying to listen for form submissions. This is what my directive currently looks like:

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');
            });
        }
    }
});

While the above method allows me to watch for the first form submission, it does not work for subsequent submissions. I attempted this solution:

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

However, the issue now is that when using the directive in a form multiple times, it only works for the first instance. I'm looking for something similar to formCtrl.onsubmit(...) or any alternative workaround to achieve the same functionality. Any help would be greatly appreciated. Thank you in advance...

Answer №1

Instead of simply monitoring the $submitted property, you have the option to create a custom directive with the same name as the built-in form directive. This custom directive can then be equipped with an event handler for form submission that triggers an angular event. The event can be captured by your myDirective directive without interfering with the default behavior of the standard form directive.

Check out the DEMO here

Alternatively, if you prefer not to extend the functionality of the form directive, you can opt for a different directive name. Just remember to add this directive name as an attribute in the form tag to trigger the desired 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 response to a query raised in the comments:

Is there an efficient way to check if the element labeled with the "my-directive" attribute also includes the "myForm" attribute (if I rename the "form" directive to "myForm") within its parent form? This would enable me to use "myDirective" with or without "myForm" attributes and adjust behavior accordingly.

There are a couple of methods to achieve this:

  1. You can utilize the .data() method in your myForm directive during compilation, and access it in the link function of your myDirective using the .inheritedData() method, provided the data assigned in the original form directive exists.

Note that passing the form controller within the broadcast in the myForm directive ensures that you receive the parent form controller for manipulation. In specific scenarios where the myDirective is nested within an ng-form, setting form.$setPristine() on the form element differs from setting it on the ngForm form controller.

View the DEMO for a practical example

.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 optimized approach involves creating a controller within the myForm directive to store form event handlers for easy iteration when a form event occurs. Instead of employing the slower $broadcast angular event, which traverses scopes downwards, this method offers a quicker alternative. By checking for the optional requirement of the ?^myForm controller in the parent element, you can efficiently implement the logic as needed. Additionally, setting the scope to true in the myForm directive enables reusability across multiple instances.

Explore the DEMO to see this implementation in action

.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();
      });
    }
  };
});

Answer №2

One option is to leverage ng-submit along with a broadcast or a similar method. Another approach could involve experimenting with $setUntouched(), or manually resetting $submitted to false once the current submission process is complete.

Answer №4

Although this post may seem outdated, I wanted to add my experience on the topic. I discovered that the form directive was not communicating properly with other directives, so I decided to consolidate everything into a single directive.

Below is a straightforward function that triggers an alert based on form.$error if the form is invalid:-

// streamlining 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();
            });
        }
    };
}])

Similar questions

If you have not found the answer to your question or you are interested in this topic, then look at other similar questions below or use the search

Obtain an array containing the keys of an object

How can I retrieve the keys of a JavaScript object as an array, using either jQuery or pure JavaScript? Is there a more concise approach than this? var data = { 'first' : 'apple', 'second' : 'banana' }; var element ...

Unable to alter state from the onClick event of a dialog button

In the process of developing a Next.js app, I encountered an interesting challenge within one of the components involving a DropdownMenu that triggers a DialogAlert (both powered by Shadcn components). The issue arises when attempting to manage the dialog& ...

Guide to showing MySQL data on an HTML page using NodeJS

Currently delving into Node JS, I am faced with the challenge of displaying fetched data from my MySQL Table in an HTML table format. Despite my efforts, I have yet to find a solution that works for me. Any help or guidance on how to resolve this issue wou ...

Using Node.js, securely encode data using a private key into a base64 format that can only be decoded on the server side

Here is my specific situation: An http request arrives at the server for a login action A user token needs to be created. This token consists of a Json object composed of different fields. It is then converted to a string and encoded in Base64. const ...

Concerns about the Dependency Tree in React

I need some assistance with my current issue. I'm having trouble installing the mui search bar component. npm i --save material-ui-search-bar Unfortunately, I'm encountering this error message: PS Z:\WebDev\ApplyWithin\frontend> ...

Exploring the power of Socket.io in conjunction with Node.js, Express, and Jade

I'm having trouble using Socket.io to test if a client is connected. I've tried different approaches and suspect that my mistake might be in the app.get function. I even attempted moving this to a separate route js file, but it didn't yield ...

Troubleshooting: Issue with Formatting While Saving Form Data in mLab Using MERN Stack

The information from my form is successfully being transferred to my mLab database, but it is not formatted as I would prefer. Currently, it appears like this: { '{"email":"<a href="/cdn-cgi/l/email-protection" class="__cf_email__" data-cfemail="5 ...

What steps can I take to avoid encountering this endless loop?

When passing foo in the arguments of useEffect to use existing array values, it causes an infinite loop because setting new values triggers the f() function again. How can this be resolved? An example of imaginary code is: const [foo, setFoo] = useState&l ...

Apollo Client's useQuery function is causing unnecessary refetches when using Next.js' router.push method

Currently, I'm facing an issue where a query within a useQuery Apollo Client hook is being re-run unnecessarily every time Next.js's router.push function is triggered. The problem code snippet looks like this: const Parent = () => { useQuery ...

JavaScript doesn't seem to be functioning properly within PHP

I have implemented the supersized jQuery script to incorporate sliding backgrounds on my WordPress page. Now, I aim to create unique slides for different sites and require a PHP if request. This is my code: <?php if ( is_page(array('Restaurant&a ...

Creating a session with AngularJS

One task I'm facing is transferring an object from one page to another. Specifically, when a user clicks on a search result on the results page, it should lead them to a page with relevant details. To achieve this, I attempted using services and facto ...

Unable to interpret the jQuery Ajax issue

My script is intended to send form data to a PHP script using ajax, but it's not working. I believe there is an error with $.ajax but I'm unsure how to locate and view the specific error. Currently, I have it set to alert 'fail' in case ...

"Transferring a JavaScript variable to Twig: A step-by-step guide for this specific scenario

When it comes to loading a CSS file based on the user's selected theme, I encountered an issue while trying to implement this in my Symfony application using Twig templates. The code worked flawlessly on a simple HTML page, but transferring it to a Tw ...

Angular's slide animation effect seems to be malfunctioning and not behaving as anticipated

Just getting started with web development and I'm attempting to create a sliding page in Angular using ng-view. However, I'm running into an issue where when page two enters, it displays below page one until page one is available. You can view th ...

What is the best way to show a message of success once the user has been redirected to the homepage?

Currently, I have a registration form utilizing AJAX and PHP for validation. Error messages can be displayed on the registration page if the user does not correctly fill out the form. Upon successful registration, the user is redirected back to the home pa ...

Determine if the input is contained within an array using JavaScript

I'm new to JavaScript and need some help with arrays. My goal is to verify if the user's input value exists in an array named "fruits" that I have declared. If it does, I want to run a specific piece of code. However, if it doesn't exist in ...

Exploring and cycling through numerous links using Selenium WebDriver in a node.js environment

Decided to switch from Casper.js to Selenium for access to more tools. Currently trying to loop through multiple links and navigate them using node.js along with selenium-webdriver. Struggling to find any helpful documentation or examples, as I keep enco ...

Ember's structured format featuring objects as rows and column names as properties

I am currently working on developing a modular tabular form that requires an input of two arrays: one containing objects (representing the rows) and another containing property names of those objects (representing the columns). The goal is to be able to mo ...

What is the process for invoking a functional component within a class-based component using the onClick event?

I am trying to display my functional component in a class-based component, but it is not working as expected. I have created a SimpleTable component which is function-based and displays a table with some values. However, I want to show this table only wh ...

Having issues with using setTimeOut within a for loop in JavaScript React

I'm currently working on a visual sorting algorithm application using React. My implementation involves bubble sort, where I generate an array of numbers from 1 to 50, shuffle them, and then apply the bubble sort method to sort them. However, I am fac ...