psycat
psycat

Reputation: 513

Angularjs: Form validation on input field generated by directive

I have built a directive that generates an input field based on meta ($scope.meta) data. My problem is supporting angular's Form validation.

Plunkr: http://plnkr.co/edit/AfR5cWyywGlCECS6h6Dr?p=preview

Explanation: directive takes a meta parameter which describes what input field type it is and other related information to render the field. All the attributes defined on directive will be copied to element and finally will replace the directive after compilation and linking.

View:

<form name="userForm">
    <!-- Username -->
    <poly-field ng-model="storage" meta="meta" key="username" name="username" ng-minlength="3" ng-maxlength="8"></poly-field>
    <p ng-show="userForm.username.$error.minlength" class="help-block">Username is too short.</p>
    <p ng-show="userForm.username.$error.maxlength" class="help-block">Username too long</p>

    <!-- Email -->
    <poly-field ng-model="storage" meta="meta" key="email" name="email" ng-required></poly-field>
    <p ng-show="userForm.email.$error.minlength" class="help-block">Email is too short.</p>
    <p ng-show="userForm.email.$error.maxlength" class="help-block">Email is too long</p>
</form>

Entering username longer or shorter than allowed should show me an error. Skipping email field show me an error too.

Here is the View source-code after applying the directive:

<form name="userForm" class="ng-pristine ng-valid ng-valid-required">
        <!-- Username -->
        <input type="undefined" ng-model="storage.username" meta="meta" key="username" name="username" ng-minlength="3" ng-maxlength="8" class="ng-scope ng-valid-minlength ng-dirty ng-valid ng-valid-maxlength">
        <p ng-show="userForm.username.$error.minlength" class="help-block ng-hide">Username is too short.</p>
        <p ng-show="userForm.username.$error.maxlength" class="help-block ng-hide">Username too long</p>

        <!-- Email -->
        <input type="undefined" ng-model="storage.email" meta="meta" key="email" name="email" ng-required="" class="ng-scope ng-valid ng-valid-required ng-dirty">
        <p ng-show="userForm.email.$error.minlength" class="help-block ng-hide">Email is too short.</p>
        <p ng-show="userForm.email.$error.maxlength" class="help-block ng-hide">Email is too long</p>
</form>

Here is the model the view and the directive working with, it's declared in ParentCtrl.

$scope.storage = {
    username: "Kitkat",
    email: "[email protected]"
};

That's the meta information, this tell how the input field look like (is it a text? date? etc.)

 $scope.meta = {
    username: { type: "text" },
    email: { type: "text" }
};

Directive declaration:

app.directive('polyField', function($compile) {
    var linkFn = function(scope, element, attributes) {
        // create input element depending on the type
        var template = document.createElement("input");
        template.type = scope.storage[attributes.key].type;

        // copy all attributes
        for (var attr in attributes.$attr) {
            if (attributes.$attr[attr] == 'ng-model') {
                template.setAttribute('ng-model', attributes[attr] + '.' + attributes.key);
            } else {
                template.setAttribute(attributes.$attr[attr], attributes[attr]);
            }
        }

        // template compilation, linking and element replacement
        template = angular.element(template.outerHTML);
        var linkTemplate = $compile(template);
        var domElement = linkTemplate(scope);
        element.replaceWith(domElement);
    }

    var directive = {
        restrict: 'E',
        scope: { 
            meta: '=',
            storage: '=ngModel',
            key: '=',
        },
        link: linkFn
    };

    return directive;
  });

Some thoughts:
Each directive creates an isolated scope, so, inside the directive userForm is unseen, however, the storage (ng-model) that is two-way binded is seen from directive's scope and parent scope, if userForm sees what's inside storage it should be ok, isn't it?

I tried to pass userForm to directive scope:

// directive
scope: {
        meta: '=',
        storage: '=ngModel',
        key: '=',
        userForm: '='
}

// template
<poly-field user-form="userForm" ng-model="storage" meta="meta" key="username" name="username" ng-minlength="3" ng-maxlength="8"></poly-field>

but with no effect.

When I look into the directives scope, into userForm field I see that the input field is actually $dirty: false, $invalid: true, and that after I typed into username/email field,

In the source code it's seems that the validation classes are applied correctly: ng-valid-minlength ng-dirty ng-invalid ng-invalid-maxlength still, no validation errors are shown.

Is there anything I can do with it?

Upvotes: 1

Views: 1509

Answers (1)

gkalpak
gkalpak

Reputation: 48212

In order for Angular's form-validation to de able to kick in, ngModel looks for a parent ngForm/form directive during its controller's instantiation phase (before the pre-linking phase).

Since you compile and link the element before adding it to the DOM there is no parent element at that time, so it fails to register.

You just need to first insert the element into the DOM and then compile and link it.

Updated plunkr

Upvotes: 2

Related Questions