1

I created an angular validator module for form validation without the need to include ngMessages in the form and everything is working as expected.

enter image description here

However I discovered a bug which I have been trying to fix. It has something to do with $compile.

So I have a directive that adds attributes to form elements and this is achieved by using $compile service however, it seems the $compile service causes an unwanted behaviour to ng-click so when ng-clicklocated inside the form is called it fires twice;

Here is the directive and what I am doing:

angular.module('app',[])

.directive('validateForm',['$compile',
        function($compile)
        {
            var addErrors = function(rules, form, ctrls, scope, config){
                //code
            };


            return {
                restrict: 'A',
                require: ['^form'],
                link: {
                    post: function(scope, element, attrs, ctrls){

                        var form = ctrls[0];
                        var config = scope.validator;

                        if(typeof config != 'object') return;

                        var rules = config['rules'];
                        var errors = [];

                        //-----
                    },
                    pre: function(scope, element, attrs, ctrls){

                        var elm = element.find('select, input, textarea').attr('validate-field','');
                        element.removeAttr("validate-form"); //remove the attribute to avoid indefinite loop
                        element.removeAttr("data-validate-form");
                        $compile(element.contents())(scope);
                    }
                }
            };
        }

    ])
 .controller('UserController',['$scope', function($scope){
  $scope.title = 'Form Validator';
  $scope.clickThings = function(value){
    alert(value); //pops up twice means ng-click fires twice
  }
}]);

Form markup:

 <div ng-controller="UserController">
 <form novalidate="" validate-form name="form" role="form">
     <div class="form-group">
          <input type="text" class="form-control" ng-model="first_name" name="first_name" />
        </div>
        <div class="form-group">
          <input type="text" class="form-control" ng-model="last_name" name="last_name" />
        </div>
        <div class="form-group">
          <input type="text" class="form-control" ng-model="email" name="email" />
        </div>
        <div class="form-group">
          <input type="password" class="form-control" ng-model="password" name="password" />
        </div>
        <div class="form-group">
          <input type="text" class="form-control" ng-model="country" name="country" />
        </div>
        <a type="button" class="btn btn-success" ng-click="clickThings('Submit Clicked')">Submit</a>
      </form>
      </div>

I have created a plunker: http://embed.plnkr.co/uIid4gczKxKI4rPOHqx7

Emeka Mbah
  • 16,745
  • 10
  • 77
  • 96

2 Answers2

1

After trying different things I realized ng-click which is already compile is compiled a second time when $compile(element.contents())(scope) is called.

To resolve this issue, only need to compile the altered/affected elements i.e elem = element.find('select, input, textarea').attr('validate-field','');

by replacing $compile(element.contents())(scope) with $compile(elem)(scope);

So I ended up with this:

angular.module('app',[])

.directive('validateForm',['$compile',
    function($compile)
    {
        var addErrors = function(rules, form, ctrls, scope, config){
            //code
        };

        return {
            restrict: 'A',
            require: ['^form'],
            link: {
                post: function(scope, element, attrs, ctrls){

                    var form = ctrls[0];
                    var config = scope.validator;

                    if(typeof config != 'object') return;

                    var rules = config['rules'];
                    var errors = [];

                    //-----
                },
                pre: function(scope, element, attrs, ctrls){

                    var elem = element.find('select, input, textarea').attr('validate-field','');
                    element.removeAttr("validate-form"); //remove the attribute to avoid indefinite loop
                    element.removeAttr("data-validate-form");
                    $compile(elem)(scope);
                }
            }
        };
    }

 ])
 .controller('UserController',['$scope', function($scope){
   $scope.title = 'Form Validator';
   $scope.clickThings = function(value){
   alert(value); //pops up twice means ng-click fires twice
  }
 }]);

Working plunker here:

Emeka Mbah
  • 16,745
  • 10
  • 77
  • 96
0

What is the purpose for using the $compile in a pre-link function? if u just want to do template transformation before the directive element get linked, u should put ur code in directive's compile and take out the $compile. Or if you would like to $compile your element after child elements are linked, u should put ur code in post-link. Putting $compile in pre-link will cause the child nodes of your child elements get linked twice.

Either choose:

angular.module('app',[])
  .directive('validateForm',['$compile',
            function($compile)
            {
                var addErrors = function(rules, form, ctrls, scope, config){
                    //code
                };


                return {
                    restrict: 'A',
                    require: ['^form'],

                    compile:function(element, attrs, ctrls){
                            // the code will be executed before get complied
                            var elm = element.find('select, input, textarea').attr('validate-field','');


                            return function(scope, element, attrs, ctrls){

                                 var form = ctrls[0];
                                 var config = scope.validator;

                                 if(typeof config != 'object') return;

                                 var rules = config['rules'];
                                 var errors = [];

                                 //-----
                        }

                        }
                };
            }

        ])

Or

angular.module('app',[])
  .directive('validateForm',['$compile',
            function($compile)
            {
                var addErrors = function(rules, form, ctrls, scope, config){
                    //code
                };


                return {
                    restrict: 'A',
                    require: ['^form'],
                    link: {
                        post: function(scope, element, attrs, ctrls){

                            var form = ctrls[0];
                            var config = scope.validator;

                            if(typeof config != 'object') return;

                            var rules = config['rules'];
                            var errors = [];

                            //-----
                            var elm = element.find('select, input, textarea').attr('validate-field','');
                            element.removeAttr("validate-form"); //remove the attribute to avoid indefinite loop
                            element.removeAttr("data-validate-form");
                            $compile(element.contents())(scope);
                        },
                        pre: function(scope, element, attrs, ctrls){


                        }
                    }
                };
            }

        ])

EDIT

Just eliminate the $compile clause will also do the trick. But these three ways have some difference.

For more information u should refer to the official document

MMhunter
  • 1,431
  • 1
  • 11
  • 18