23

I have a directive that I use like this:

<dir model="data"></dir>

The directive has an isolated scope.

scope :{
  model:'='
}

Now I'm trying to use ng-show on that directive using another attribute of my page's $scope, like this:

<dir ng-show="show" model="data"></dir>

But it's not working because the directive is trying to find the show attribute on its own scope.

I don't want the directive to know about the fact that its container might choose to hide it.

The workaround I found is to wrap the directive in a <div> and apply ng-show on that element, but I don't like the extra element this forces me to use:

<div ng-show="show" >
  <dir model="data"></dir>    
</div>

Is there a better way of doing this?

See this plunker: http://plnkr.co/edit/Q3MkWfl5mHssUeh3zXiR?p=preview

Sylvain
  • 19,099
  • 23
  • 96
  • 145

4 Answers4

12

Update: This answer applies to Angular releases prior to 1.2. See @lex82's answer for Angular 1.2.

Because your dir directive creates an isolate scope, all directives defined on the same element (dir in this case) will use that isolate scope. This is why ng-show looks for property show on the isolate scope, rather than on the parent scope.

If your dir directive is truly a standalone/self-contained/reusable component, and therefore it should use an isolate scope, your wrapping solution is probably best (better than using $parent, IMO) because such directives should normally not be used with other directives on the same element (or you get exactly this kind of problem).

If your directive doesn't need an isolate scope, your problem goes away.

Community
  • 1
  • 1
Mark Rajcok
  • 362,217
  • 114
  • 495
  • 492
11

You could consider migrating to Angular 1.2 or higher. The isolate scope is now only exposed to directives with a scope property. This means the ng-show is not influenced by your directive anymore and you can write it exactly like you tried to do it in the first place:

<dir ng-show="show" model="data"></dir>

@Angular-Developers: Great work, guys!

lex82
  • 11,173
  • 2
  • 44
  • 69
  • It works indeed. See same plnkr w/ 1.2 in action here: http://plnkr.co/edit/ARAqyErwwkINvIDn1TwF?p=preview – Sylvain Jan 28 '14 at 21:24
4

Adding the following into the link function solves the problem. It's an extra step for the component creator, but makes the component more intuitive.

function link($scope, $element, attr, ctrl) {

    if (attr.hasOwnProperty("ngShow")) {
        function ngShow() {
            if ($scope.ngShow === true) {
                $element.show();
            }
            else if($scope.ngShow === false) {
                $element.hide();
            }
        }
        $scope.$watch("ngShow", ngShow);
        setTimeout(ngShow, 0);
    }
    //... more in the link function
}

You'll also need to setup scope bindings for ngShow

scope: {
    ngShow: "="
}
Scott Boring
  • 2,601
  • 1
  • 25
  • 31
1

Simply use $parent for the parent scope like this:

<dir ng-show="$parent.show" model="data"></dir>

Disclaimer

I think that this is the precise answer to your question but I admit that it is not perfect from an aesthetical point of view. However, wrapping the <div> isn't very nice either. I think one can justify it because from the other parameter passed to the isolate scope, it can be seen that the directive actually has an isolate scope. On the other hand, I have to acknowledge that i regularly forget the $parent in the first place and then wonder why it is not working.

It would certainly be clearer to add an additional attribute is-visible="expression" and insert the ng-show internally. But you stated in your question that you tried to avoid this solution.

Update: Won't work in Angular 1.2 or higher.

lex82
  • 11,173
  • 2
  • 44
  • 69