AngularJS Nested Directives with Isolate Scope
01 October 2014
I’ve been playing around a bit with AngularJS lately, of which the learning curve has been accurately described as an emotional roller coaster.
I came across an issue with nested directives that others have faced as well. This particular Stack Overflow question is still open but around six months old so I’m guessing it’s probably been abandoned at this point.
I have two directives nested inside one another, and I want to hide or remove the outer div/directive based on a property on the directive scope. The button simply toggles the ‘isEnabled’ property on the controller scope. Something like this:
For the purposes of demonstration, I’m passing my view model data into the directive as-is from the controller scope. I might otherwise want to pass only a subset of the view model data that relates only to the directives.
If I run this (fiddle) it clearly doesn’t work properly. The toggle button works as expected, but the output is:
[Toggle] Hi, Bert! Hi, !
The behaviour seems to relate to what’s happening behind the scenes regarding the difference between ng-if and ng-show.
ng-if vs ng-show
The ng-if directive is removing and recreating a portion of the DOM depending on the expression provided. This behaviour is similar to jQuery’s $(‘.elem’).remove() and .append() functions. In my case it appears that when the DOM fragment is recreated the transcluded inner directive expression is evaluated in the outer directive scope.
Contrast this behaviour with the ng-show directive, which uses CSS to show or hide a portion of the DOM depending on the expression provided. The ng-show behaviour more closely resembles jQuery’s $(‘.elem’).show() and .hide() functions. Visible or hidden elements remain within the DOM, so the inner directive expression appears to be evaluated within the controller scope.
There are a few different ways to get the correct behaviour I’m looking for.
1. Move the ng-if to the view markup
The ng-if directive can be moved outside of the outer directive template to the view. This works for a simple scenario, but the directive may have more complex removal logic internally than just a flag.
2. Pass the inner directive model differently
The inner directive model can be referenced in the view markup as if it was a part of the outer directive template. I don’t like this way because it breaks the encapsulation of the outer directive code.
3. Don’t transclude in the outer directive
It’s possible to move the inner directive span element from the view markup to inside the directive template to have truly nested directives. This solves the encapsulation issue, however it may not be an option if the intention is to customise some part of the outer directive content from the view.
4. Use ng-show instead of ng-if
The inner directive model can be passed directly from the controller when using ng-show.
Perhaps there’s a better option? Let me know!
comments powered by Disqus