3

How do I create an angular directive that adds a input in a form but also works with form validation.

For instance the following directive creates a text input:

    var template = '\
<div class="control-group" ng-class="{ \'error\': {{formName}}[\'{{fieldName}}\'].$invalid}">\
    <label for="{{formName}}-{{fieldName}} class="control-label">{{label}}</label>\
    <div class="controls">\
        <input id="{{formName}}-{{fieldName}}" name="{{fieldName}}" type="text" ng-model="model" />\
    </div>\
</div>\
';

angular

    .module('common')

    .directive('formTextField', ['$compile', function ($compile) {
        return {
            replace: true,
            restrict: 'E',
            scope: {
                model: '=iwModel',
                formName: '@iwFormName',
                fieldName: '@iwFieldName',
                label: '@iwLabel'
            },
            transclude: false,
            template: template
        };
    }])

;

However the input is not added to the form variable ($scope.formName). I've been able to work around that by using the dynamicName directive from the following stackoverflow question Angularjs: form validation and input directive but ng-class will still not work.


Update

It now seems to be working but it feels like a hack. I thought I needed the scope to grab the form and field names however I can read that directly from the attributes. Doing this shows the field in the controllers scope form variable. However the ng-class attribute does not apply. The only way around this is to add the html element a second time once the scope is available.

jsFiddle here

    var template = '\
<div class="control-group">\
    <label class="control-label"></label>\
    <div class="controls">\
        <input class="input-xlarge" type="text" />\
    </div>\
</div>\
';

angular

    .module('common')

    .directive('formTextField', ['$compile', function ($compile) {
        return {
            replace: true,
            restrict: 'E',
            scope: false,
            compile: function compile(tElement, tAttrs, transclude) {
                var elem = $(template);
                var formName = tAttrs.iwFormName;
                var fieldName = tAttrs.iwFieldName;
                var label = tAttrs.iwLabel;
                var model = tAttrs.iwModel;
                elem.attr('ng-class', '{ \'error\': ' + formName + '[\'' + fieldName + '\'].$invalid }');
                elem.find('label').attr('for', formName + '-' + fieldName);
                elem.find('label').html(label);
                elem.find('input').attr('id', formName + '-' + fieldName);
                elem.find('input').attr('name', fieldName);
                elem.find('input').attr('ng-model', model);

                // This one is required so that angular adds the input to the controllers form scope variable
                tElement.replaceWith(elem);

                return {
                    pre: function preLink(scope, iElement, iAttrs, controller) {
                        // This one is required for ng-class to apply correctly
                        elem.replaceWith($compile(elem)(scope));
                    }
                };

            }
        };
    }])

;
1
  • There is solution which should help you. I needed the same, when building dynamic directives... Commented Sep 10, 2014 at 5:09

1 Answer 1

1

when I do something like this, I use the directive compile function to build my html prior to it being processed. For example:

myApp.directive('specialInput', ['$compile', function($compile){
    return {
        // create child scope for control
        scope: true,
        compile: function(elem, attrs) {
            // use this area to build your desired dom object tree using angular.element (eg:)
            var input = angular.element('<input/>');

            // Once you have built and setup your html, replace the provided directive element
            elem.replaceWith(input);

            // Also note, that if you need the regular link function, 
            // you can return one from here like so (although optional)
            return function(scope, elem, attrs) {
                // do link function stuff here
            };
        }
    };
}]);
Sign up to request clarification or add additional context in comments.

3 Comments

Doesn't quite work still. I thought I needed scope for the field name meaning I could only do this in link() or compile pre/post however I can get around this by reading the attributes directly. However this still fails on ng-class. The only way I've found around this so far is to add the element a second time once scope available. I'll edit my answer with the code.
Not sure if you copy and pasted your code directly, as there is a type for ng-class (no } ). Either way, if it still doesn't work, are you able to put a fiddle together so we can play with it?
Sorry, fixed the typo. Updated the question and here's a fiddle: jsfiddle.net/chrish/Lphpxh5z

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.