StevieP
StevieP

Reputation: 401

AngularJS: Add class if child has that class

For a dynamically created "sidenav" navigation menu, I would like to add the .active class to a parent <li> if one (or more) of it's children <li> are also .active.

What would be the best practice angular way to do this? I have tried the following - none of which I've got to work:

1. ngClass - passing the ng-repeated element to a controller function

HTML:

<nav sidenav>
  <ul>
    <li ng-repeat="item in items" ng-class="{active: hasActive(this)}"> 
      {{item.name}} 
      <ul> ... children li's not shown ... </ul>
    </li>
  </ul>
</nav>

JS within the sidenav directive's controller:

$scope.hasActive() = function (elem) {
  return $(elem).has('.active').length ? true : false;
}

I presume this doesn't work because you can't access the ng-repeated element - at least not like I'm trying to.

2. Directive and ng-class

HTML

<nav sidenav>
  <ul>
    <li ng-repeat="item in items" activate ng-class={active: hasActive()}> 
      {{item.name}} 
      <ul> ... children li's not shown ... </ul>
    </li>
  </ul>
</nav>   

Directive activate's link function:

scope.hasActive = function () {
  return $(elem).has('.active').length ? true : false;
}

I guess the hasActive function isn't called when the children's class change. So I tried...

3. Directive and watch function

HTML:

<li ng-repeat="item in items" activate>  ... </li>

activate's link function:

var activeChildren = $(elem).has('.active');
scope.$watchCollection(activeChildren, function (newVal, oldVal) {
  if (newVal) {
  elem.addClass('active');
  } else {
  elem.removeClass('active');
  }
});

No idea why this one doesn't work!

So, time to ask the community before reverting to a jQuery method of adding the class to the parent at the same time as adding the class to the child (which I'd like to avoid). (Yes, I'm aware I've used jQuery above... I don't believe the has() function is in Angular's jqLite - and I'm using it for other things anyway.)

Any other methods for creating the desired result would be welcome... acknowledging there are existing SO posts for these.

UPDATE: Working solution using $emit and $on

I've added this to the code which adds the .active class to the children (which is in a child's directive):

function activate () {
  elem.addClass('active');
  scope.$emit ('activated');
}
function deactivate () {
  elem.removeClass ('active');
  scope.$emit ('deactivated');
}

and in the parent's activate directive:

scope.$on('activated', function() {
  elem.addClass('active');
});
scope.$on('deactivated', function () {
  elem.removeClass('active');
});

($emit broadcasts the event up the scope heirarchy)

This may not be the best practice angular way, or the most efficient code, so I'll leave the question open if anyone has any better suggestions.

Upvotes: 1

Views: 3204

Answers (1)

Yaron Schwimmer
Yaron Schwimmer

Reputation: 5357

Your 3rd option can't work because if you use $scope.$watchCollection(activeChildren), then activeChildren should be a scope variable. Also, it should be $scope.$watchCollection('activeChildren')

So as a start, I would suggest trying $scope.activeChildren = $(elem).has('.active'); instead.

If that doesn't work, consider the following enhancement to your 3rd attempt:

$scope.$watch(function() {
    return $(elem).has('.active');
}, function(newVal) {
    if (newVal && newVal.length > 0) {
        elem.addClass('active');
    }
    else {
        elem.removeClass('active');
    }
});

Upvotes: 1

Related Questions