How do you use dynamic ng-show values inside a directive template?

All we need is an easy explanation of the problem, so here it is.

I am learning angular, and I am trying to reduce some code that it takes to do some common things, like display error messages, by using angular directives.

One directive I would like to create is like this:

<error-message name="paymentPlanForm.position" error="required">
    This field is required.
</error-message>

This would generate the following:

<p ng-show="paymentPlanForm.position.$dirty && paymentPlanForm.position.$error.required">
    <span class="fontawesome-remove"></span> This field is required.
</p>

I started to write a directive to accomplish this as follows:

app.directive("errorMessage", function() {
    return {
        restrict: 'E',
        transclude: true,
        replace: true,
        templateUrl: 'views/partials/errorMessage.html',
        link: function(scope, element, attributes) {
            scope.name = attributes.name;
            scope.error = attributes.error;
        }
    }
});

The template is as follows:

<p ng-show="{{name}}.$dirty && {{name}}.$error.{{error}}">
    <span class="fontawesome-remove"></span>
    <span ng-transclude></span>
</p>

I thought this would work, but Angular seems to crash when trying to parse ng-show inside the template:

Error: [$parse:syntax] Syntax Error: Token '.' not a primary expression at column 1 of the expression [.$dirty && .$error.] starting at [.$dirty && .$error.].
http://errors.angularjs.org/1.2.9/$parse/syntax?p0=.&p1=not%20a%20primary%20expression&p2=1&p3=.%24dirty%20%26%26%20.%24error.&p4=.%24dirty%20%26%26%20.%24error.
minErr/<@http://localhost:8080/keiko/vendor/js/angular.js:78

When I inspect the element in Firebug, the dynamic values have been passed in successfully, but I guess there’s a problem with the scope, or something else.

How can I get angular to do what I want?

How to solve :

I know you bored from this bug, So we are here to help you! Take a deep breath and look at the explanation of your problem. We have many solutions to this problem, But we recommend you to use the first method because it is tested & true method that will 100% work for you.

Method 1

The problem is that your link function runs after the template has been compiled by Angular. So name and error haven’t been set during compilation when ngShow checks its attributes (thus the error where it see’s a “.” without anything in front of it).

ngShow only looks at its attributes at compile, it then watches whatever expression was passed in at that point. So it never sees that the link function changes its attribute.

The html has been updated by the time you look at it, which makes it all the more confusing.

My recommendation is to use an isolate scope and pass those two attributes in that way. That’ll address the timing, plus it’s not a bad idea to use isolate scopes for this kind of directive anyway:

scope:{
        name: '@',
        error: '@'
     },

The one trade off is now the form data will be on the directive’s parent’s scope, so we’ll need to add a $parent reference within your template:

template: '<div><p ng-show="$parent.{{name}}.$dirty">Dirty</p><p ng-show="$parent.{{name}}.$error.{{error}}"><span ng-transclude></span></p></div>',

Note I tweaked your template to separate the dirty and the required tests just to make it easier to see it working.

Here’s a working fiddle

Note: Use and implement method 1 because this method fully tested our system.
Thank you 🙂

All methods was sourced from stackoverflow.com or stackexchange.com, is licensed under cc by-sa 2.5, cc by-sa 3.0 and cc by-sa 4.0

Leave a Reply