angularjsangularjs-directiveangularjs-compile

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


Solution

  • 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.