1
votes

I'm creating inputs inside a form dynamically. I created this directive for the purpose:

// Generate inputs on the fly using BE data.
.directive('inputGenerator', ['$compile', function ($compile) {
    return {
        restrict: 'E',
        scope: {
            id: '@',
            type: '@',
            name: '@',
            attributes: '=',
            ngModel: '=',
            ngDisabled: '='
        },
        link: function (scope, iElement, iAttrs) {
            // Get attributes
            var id         = iAttrs.id,
                type       = iAttrs.type,
                name       = iAttrs.name;

            scope.ngModel[name] = {};

            var extended_attributes = {
              "type": type,
              "id": id,
              "data-ng-model": 'ngModel.' + name + '[\'value\']',
              "name": name,
              "data-ng-disabled": "ngDisabled"
            };

            if ( ! scope.attributes) {
                scope.attributes = {};
            }

            // Append extra attributes to the object
            angular.extend(scope.attributes, extended_attributes);

            // Generate input
            var input = '<input ';

            angular.forEach(scope.attributes, function (value, key) {
                input += key + '="' + value + '" ';
            });

            input += '/>';

            // Compile input element using current scope (directive) variables
            var compiledElement = $compile(input)(scope);

            // Set the file selected name as the model value
            compiledElement.on('change', function () {
                if (this.files && this.files[0].name) {
                    var that = this;
                    scope.$apply(function () {
                        scope.ngModel[name] = {};
                        scope.ngModel[name]['value'] = that.files[0].name;
                    });
                }
            });

            // Replace directive element with generated input
            iElement.replaceWith(compiledElement);
        }
    };
}]);

This html line will trigger the directive:

<input-generator data-name="{{ item.name }}" data-ng-model="inputs.sources" data-attributes="item.attrs" data-type="{{ item.type }}" data-id="inputFile_{{ $index }}" data-ng-disabled="inputs.sources[item.name].selected" />

I'm running on Angular 1.4.3.

Problem

The model and pretty much everything works fine in the directive, but for some reason the form remains valid when the input added is invalid as you can see in this image.

enter image description here

I already tried:

Any of the Angular features of form validation works

I debugged Angular and seems to be that the input attached to the form is different from the input compiled inside the directive.

I already called formName.$setPristine() after each input was created, but it didn't work.

I couldn't access the form from the directive, but I think is not a good idea either.

I already wrapped the input with a ng-form tag, but nothing useful comes out of that.

I tried to use the directive compile method, but this is just triggered once when the app loads and I've a select input that loads different inputs on change.

Any help on this is much appreciated! :)

Thank you to everyone for contribute anyways!!

5

5 Answers

1
votes

You should definitely take a look at my Angular-Validation Directive/Service. It as a ton of features and I also support dynamic inputs validation, you can also pass an isolated scope which helps if you want to not only have dynamic inputs but also have dynamic forms, also good to use inside a modal window.

For example, let's take this example being a dynamic form and inputs defined in the Controller:

$scope.items.item1.fields = [
   {
      name: 'firstName',
      label:'Enter First Name',
      validation:"required"
    },
    {
      name: 'lastName',
      label: 'Enter Last Name',
      validation:"required"
    }
  ];
  $scope.items.item2 = {
    heading:"Item2",
    formName:"Form2"
  };
  $scope.items.item2.fields = [
   {
      name: 'email',
      label:'Enter Email Id',
      validation:"required"
    },
    {
      name: 'phoneNo',
      label: 'Enter Phone Number',
      validation:"required"
    }
];

It will bind the validation to the elements and if you want to easily check for the form validity directly from the Controller, simply use this

var myValidation = new validationService({ isolatedScope: $scope });

function saveData() {
    if(myValidation.checkFormValidity($scope.Form1)) {
        alert('all good');
    }
}

You can also use interpolation like so

<input class="form-control" type="text" name="if1"
        ng-model="vm.model.if1"
        validation="{{vm.isRequired}}" />

Or using a radio/checkbox to enable/disable a field that you still want to validate when it becomes enable:

ON <input type="radio" ng-model="vm.disableInput4" value="on" ng-init="vm.disableInput4 = 'on'">
OFF <input type="radio" ng-model="vm.disableInput4" value="off">

<input type="text" name="input4" 
        ng-model="vm.input4" 
        validation="alpha_dash|min_len:2|required" 
        ng-disabled="vm.disableInput4 == 'on'" />

It really as a lot of features, and is available on both Bower and NuGet (under the tag name of angular-validation-ghiscoding). So please take a look at my library Angular-Validation and a live demo on PLUNKER.

It's loaded with features (custom Regex validators, AJAX remote validation, validation summary, alternate text errors, validation on the fly with the Service, etc...). So make sure to check the Wiki Documentation as well... and finally, it's fully tested with Protractor (over 1500 assertions), so don't be afraid of using in production.

Please note that I am the author of this library

1
votes

I ran into this issue as well with Angular v1.5.9. The main issue here is that you are compiling the HTML template before it is in the DOM, so Angular doesn't know it's part of the form. If you add the HTML first, then compile, Angular will see your input as a form child, and it will be used in the form validation.

See similar answer to Form Validation and fields added with $compile

Don't do:

        var myCompiledString = $compile(myHtmlString)(scope);
        // ...
        element.replaceWith(myCompiledString);

Do this instead:

        var myElement = angular.element(myHtmlString)
        element.replaceWith(myElement) // put in DOM
        $compile(myElement)(scope) // $compile after myElement in DOM

Note: I swapped the more conventional element for OP's iElement, which is a jQLite reference of the directive's HTML element

0
votes

you need to use ng-form directive as a wrapper for your input.

you can see an example of this here

0
votes

but it works for me. You can pass the form reference to the directive and use it directly.

in the code below, the scope.form is used to know the general state of the form, and the scope.name to access the input field state.

< ng-form name="{{name}}" ng-class="{error: this[name][name].$invalid && form.$submitted}" >

i hope it helps

0
votes

you need to set name of control dynamic and use this dynamic name for form validation. in the following e.g. you see the dynamic name and id of control and used for the validation of angular (using ng-massages) more details see http://www.advancesharp.com/blog/1208/dynamic-angular-forms-validation-in-ngrepeat-with-ngmessage-and-live-demo