David Smith David Smith - 2 months ago 20
AngularJS Question

AngularJS ng-if not deleting element after $compile

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

Answer

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.