Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Angular Directive: Adding ng-class directive at compile time on existing template element

Long story short, the idea is to simplify templating by not having to manually add ng-class={'has-error': 'formName.inputName.$invalid'} on every single form-group

So I want to create a directive that will generate a string that will be added to a template element. That string is an ng-class attribute with an expression

I thought that creating a quick directive that adds the ng-class attribute during the compile phase would be enough, but it doesn't seem to cut it.

Directive Definition Object

{
    restrict: 'C',
    compile: function(tElement, tAttrs) {
        var $elem = angular.element(tElement),
            formName = $elem.parents('[ng-form]').length ? $elem.parents('[ng-form]').attr('ng-form') : $elem.parents('form').attr('name'),
            controlName = $elem.find('.form-control').attr('name');

        $elem.attr('ng-class', '{"has-error": ' + formName + '.' + controlName + '.$invalid}');

        console.log('added ng-class attr', $elem.attr('ng-class'));
    }
}

Plunk here

See the console

The 1st form-group has its ng-class attribute added dynamically.
The expression associated with it is correct
But the ng-class directive is never executed

The 2nd form-group has its ng-class attribute added manually in the template and works as expected, obvisouly

--

What am I missing here?

like image 486
Olivier Clément Avatar asked Dec 03 '15 21:12

Olivier Clément


2 Answers

Managed to make it work without adding extra watchers (granted, ng-class uses a watcher, but after testing the proposed solution I saw an increase in watchers amount)

I had to $compile the new DOM element in postLink, but to avoid recursion I need to remove the directive itself. This prevents me to reuse a class-name such as "form-group"

Here's the working Directive

function bsFormClass($compile) {
  return {
    restrict: 'A',

    compile: function (tElement) {

        var $elem = angular.element(tElement),
            controlName = $elem.find('.form-control').attr('name');

        if(!controlName) { return; }

        var formName = $elem.parents('[ng-form]').length ? $elem.parents('[ng-form]').attr('ng-form') : $elem.parents('form').attr('name');

        $elem.attr('ng-class', '{"has-error": ' + formName + '.' + controlName + '.$invalid}');
        $elem.removeAttr('bs-form-class');

        return {
            post: function(scope, elem, attrs) {
                $compile(elem)(scope);
            }
        }
    }
  }
}

Thanks for you help all

like image 122
Olivier Clément Avatar answered Oct 19 '22 01:10

Olivier Clément


You cannot add directives to the current element!

In your case the solution is easy though; just watch the appropriate expressions and add the class as necessary:

function formGroupClass () {
    return {
        restrict: 'C',
        link: function(scope, elem, attrs) {
            var formName = elem.parents('[ng-form]').length ? elem.parents('[ng-form]').attr('ng-form') : elem.parents('form').attr('name'),
                controlName = elem.find('.form-control').attr('name');

            scope.$watch(formName + '.' + controlName + '.$invalid', function(newval) {
              elem.toggleClass('has-error', !!newval);
            });
        }
    };
}

The ng-class would add a watch anyway... See forked plunk: http://plnkr.co/edit/oKa6CKFoF1T5WoDzIPkI?p=preview

like image 20
Nikos Paraskevopoulos Avatar answered Oct 19 '22 00:10

Nikos Paraskevopoulos