Matt Way
Matt Way

Reputation: 33199

Issues getting validation working with dynamically generating form inputs?

I have some basic form/input html that works (including validation) if explicitly written as follows:

<form name="forms.create" novalidate>
    <div class="si-container">
        <div class="si-input-container">
            <input class="si-input" name="someNum" placeholder="Enter a number" ng-model="formdata.number" type="number" min="40"/>
        </div>
        <div class="si-error">
            <div ng-show="forms.create.someNum.$error.min">Error! Value must be > 40.</div>
        </div>
    </div>
</form>

Now what I want to do is create a directive that allows me to write the html below, but result in the html above:

<form name="forms.create" novalidate>
    <div special-input name="someNum" placeholder="Enter a number" type="number" ng-model="formdata.number">
        <div error-type="min" error-value="40">Error! Value must be > 40.</div>
    </div>
</form>

My attempt at the special-input directive (simplified) is as follows:

.directive('specialInput', [function(){
    return {
        compile: function(elem, attrs){

            var input = angular.element('<input class="si-input"/>');

            input.attr('placeholder', attrs.placeholder);
            input.attr('type', attrs.type);
            input.attr('name', attrs.name);
            input.attr('ng-model', attrs.ngModel);

            var errorCont = angular.element('<div class="si-error"></div>');

            var errors = elem.children();
            angular.forEach(errors, function(error){
                var err = angular.element(error);
                var type = err.attr('error-type');
                var value = err.attr('error-value');
                input.attr(type, value);

                var formName = elem.parent().attr('name');
                errorCont.append('<div ng-show="' + formName + '.' + attrs.name + '.$error.' + type + '">' + err.html() + '</div>');
            });

            var cont = angular.element('<div class="si-container"></div>');
            cont.append('<div class="si-floating-label">' + attrs.placeholder + '</div>');
            cont.append('<div class="si-input-container">' + input[0].outerHTML + '</div>');
            cont.append('<div class="si-underline"></div>');  
            cont.append(errorCont);              

            elem.replaceWith(cont[0].outerHTML);
        }
    };
}]);

Now the resultant html using the directive above looks about right. If I put {{formdata.number}} below the form the value changes as expected. The problem is that now the validation never shows.

For example, if I put the value 5 in the input and inspect the form object, I get weird results. $dirty is set to true for form, but not for form.someNum. If I put 55 in the input, $dirty is still set to false for form.someNum, but $modelValue and $viewValue both show 55.

Any ideas or suggestions? Here is a fiddle to help with any testing. If you put 50 in the input box you should see the value below, but put 5 and the error does not appear


UPDATE

I have managed to get it working by moving the dom changes into the link function instead of the compile function, and adding this:

elem.replaceWith(cont);
$compile(cont)(scope);

I am still puzzled though, as to why this works, while altering the dom in the exact same way in the compile function doesn't work. Is anyone able to explain this?

Upvotes: 0

Views: 96

Answers (1)

runTarm
runTarm

Reputation: 11547

It's because the original ng-model is still get compiled even the original DOM has already been replaced by the new one in your compile function.

The ng-model directive will register itself to a parent form in its postLink function. Due to the fact that the postLink function will be executed in reverse (child's before parent's), the new ng-model will do the registration first, thus it will be overridden by the one from the original ng-model eventually.

To avoid this problem, you could change the original ng-model to another name such as my-model, then rename it to ng-model later in your compile function.

Example jsfiddle: http://jsfiddle.net/Wr3cJ/1/

Hope this helps.

Upvotes: 1

Related Questions