4

I have a directive that does the following:

  1. Adds another directive attribute to the element.
  2. Removes its own attribute.
  3. Calls $compile() on the element to make AngularJS re-compile the element so the new directive is attached.

This works fine, except when I also add an ng-if to the element. See this minimal example and follow the steps below to demonstrate.

https://embed.plnkr.co/ymk0RwGopGF1KvesWmvA/

  1. Press + any number of times to add to "count".
  2. Press 0 to reset "count".
  3. Press + any number of times again.

I'd expect the "my-test shown" <p> tag to be deleted from the DOM once its ng-if condition is no longer true after step #2. Instead, it stays around, and you'll see an extra copy of the message after step #3.

I assume calling $compile($element)($scope); in the my-test directive link function is having some unintended consequence, but I don't understand what's going on here. Any ideas?

Thanks, David

3 Answers3

3

As far as I can understand, when you change the value of count with 0, your directive is destroyed before changing the value of count. So that unremoved directive's count value is still 1.

If you use ngShow instead of ngIf, you can solve this. Because ngShow attribute doesn't trigger $destroy event and doesn't remove element from your view. So that directive can catch new value of that count. Or you can use prelink instead of link for catching updating of value of count.

Ali BARIN
  • 1,870
  • 19
  • 22
  • I also tried ng-show prior to posting, and I agree it solves the problem in the minimalistic example. In my real app, however, there are situations which require actually using ng-if, such as for form inputs that are required when visible. – David Smith Sep 20 '16 at 19:21
  • @DavidSmith now I remembered. You can renamed that `link` property with `prelink`. After this changing, your ngIf will be able to catch the value of `count`. You can solve this by this way. – Ali BARIN Sep 20 '16 at 19:24
1

I dont know how to explain properly but ng-if adds a new scope to the element, his own scope, check this so question to see more details: what is the difference between ng-if and ng-show/ng-hide. I tried with ng-show and it worked the way you want:

ng-show="count > 0"

Hope it helps =)

Community
  • 1
  • 1
Gustavo Gabriel
  • 1,378
  • 2
  • 17
  • 28
1

As others have answered, the short solution is to use ng-show instead of ng-if or to not use $compile like that. With that aside, you might have your good reasons why you would want to use ng-if and $compile like this.

This question interested me on the note of using $compile with an isolate scope from ng-if. I did a bit of experimenting with this fork and will try to explain what I found.

We already know ng-if creates an isolate scope, but then passing that element with ng-if on it through $compile creates another isolate scope (and would make the newly compiled ng-if be looking at variables on the first-round isolate scope - the directive's $scope value).

To re-iterate that, we're having some scopes looking like (value in [] is scope.$id):

  1. main/outer controller has scope[2]

  2. ng-if my-test element has ng-if looking at scope[2].count and creates scope[3]

  3. my-test linker therefore has $scope.$id == 3;

  4. my-test does $compile - recompiled ng-if element: creates new isolate scope[4] and is looking at scope[3].count

  5. when scope[2].count hits 0 - scope[3] gets $destroyed (because scope[3] was created by that first ng-if which is still lingering around somewhere) ... BUT! the element is A. still there and B. its count isn't updating - WHY?

Well because the element that's still there is the one that was $compiled and has A. an ng-if looking at scope[3].count (which is now $destroyed) and B. its own new isolate scope[4] (created by re-compiling ng-if element with parent scope[3])

So ya. That is all very confusing and you might just be asking... well how do I fix this??

TL;DR;

The simplest solution: $element.removeAttr('ng-if'); before you do $compile($element)($scope);

If you've been following along, this works because the original ng-if is still looking at scope[2].count, and the element that is present is no longer getting a second isolate scope.

plong0
  • 2,140
  • 1
  • 19
  • 18
  • That was very helpful, thanks. Do other built-in directives create an isolated scope like ng-if? Am I likely to hit this same issue again if I happen to use ng-repeat or even a custom/3rd party directive alongside my directive? – David Smith Sep 20 '16 at 19:50
  • Glad that it was helpful! Some built-in and 3rd party directives do create isolate scope. You will just need to review the docs for each directive you plan to use. For example, [ng-if docs](https://docs.angularjs.org/api/ng/directive/ngIf) say "This directive creates new scope." (under Directive Info section) As do the [ng-repeat docs](https://docs.angularjs.org/api/ng/directive/ngRepeat), but you will see [ng-show](https://docs.angularjs.org/api/ng/directive/ngShow) (for example) does not. – plong0 Sep 20 '16 at 21:30