Reputation: 401
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:
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.
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...
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.
$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
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