Reputation: 75
I have a series of list items populated by ng-repeat. Visibility is controlled by a simple ng-click and ng-show relationship. For the most part, this works just fine, but I want to be able to control the show/hide behavior with a global button that will show or hide all available items on the list.
Fair disclosure: I'm still very new to AngularJS. I'm aware that this is a scoping issue, but I'm not sure how to solve it. This is almost certainly a case of not knowing the right question to ask.
I have a jsfiddle here demonstrating my difficulty: http://jsfiddle.net/36BYs/838/
Sample HTML:
<div ng-controller="MainCtrl">
<span ng-show="!IsVisible" ng-click="isVisible = !isVisible;" >
(show/hide all)
<i class="fa fa-minus-square-o fa-small"></i>
</span>
<ul>
<li ng-repeat="mentor in mentors">
<a ng-click="isVisible = !isVisible;">show/hide</a>
<span ng-show="isVisible">{{mentor}}</span>
</li>
</ul>
</div>
Sample JS:
var app = angular.module('myApp', []);
function MainCtrl( $scope ) {
$scope.isVisible = true;
$scope.mentors = [ 'Jonathan', 'Nathan', 'Chris', 'Brian', 'Timothy' ];
}
It works fine as long as you have not independently toggled one of the list items. but If you click to show/hide on a particular line, the global ng-click loses control of the item.
Thanks in advance for any advice you can offer.
Upvotes: 2
Views: 77
Reputation: 75
While other comments have been useful, user miqid's comment provided the best answer to my specific need:
As you're aware, ng-repeat introduces a separate scope so that each isVisible underneath no longer tracks the parent isVisible. One solution is to explicitly track isVisible per item in addition to tracking a parent visibility state that overrides item-level one if necessary. Demo—jsfiddle.net/uLykhg0z – miqid 14 hours ago
The jsfiddle is a variation on Andrew Shepherd's solution (jsfiddle.net/uLykhg0z):
HTML:
<div ng-controller="MainCtrl">
<span ng-show="!IsVisible" class = "clickable" ng-click="toggleVisibility()" >
(show/hide all)
<i class="fa fa-minus-square-o fa-small"></i>
</span>
<ul>
<li ng-repeat="mentor in mentors">
<a class = "clickable" ng-click="mentor.isVisible = !mentor.isVisible;">show/hide</a>
<span ng-show="mentor.isVisible">{{mentor.name}}</span>
</li>
</ul>
</div>
JS:
var app = angular.module('myApp', []);
function MainCtrl( $scope ) {
var isVisible = true;
$scope.mentors = [
{ name: 'Jonathan', isVisible: true },
{ name: 'Nathan', isVisible: true },
{ name: 'Chris', isVisible: true },
{ name: 'Brian', isVisible: true },
{ name: 'Timothy', isVisible: true },
];
$scope.toggleVisibility = function () {
isVisible = !isVisible;
$scope.mentors = $scope.mentors.map(function (mentor) {
mentor.isVisible = isVisible;
return mentor;
});
};
}
This allowed me to accommodate some more complex nesting that I have to deal with in a tree structure.
Thanks again for all of your help, folks!
Upvotes: 0
Reputation: 6965
var app = angular.module('myApp', []);
app.controller("myCtrl", function ($scope) {
$scope.mentors = [
'Jonathan',
'Nathan',
'Chris',
'Brian',
'Timothy'
];
$scope.showAll = function() {
$scope.visibleMentors = $scope.mentors.slice();
};
$scope.hideAll = function() {
$scope.visibleMentors = [];
};
$scope.isVisible = function(mentor) {
return $scope.visibleMentors.includes(mentor);
};
$scope.show = function(mentor) {
$scope.visibleMentors.push(mentor);
};
$scope.hide = function(mentor) {
$scope.visibleMentors.splice($scope.visibleMentors.indexOf(mentor), 1);
};
$scope.showAll();
});
a {
cursor: pointer;
}
<script src="https://ajax.googleapis.com/ajax/libs/angularjs/1.0.8/angular.min.js"></script>
<div ng-app="myApp">
<div ng-controller="myCtrl">
<button ng-click="showAll()">show all</button>
<button ng-click="hideAll()">hide all</button>
<ul>
<li ng-repeat="mentor in mentors">
<a ng-show="isVisible(mentor)" ng-click="hide(mentor)">hide</a>
<a ng-show="!isVisible(mentor)" ng-click="show(mentor)">show</a>
<span ng-show="isVisible(mentor)">
{{mentor}}
</span>
</li>
</ul>
</div>
</div>
Upvotes: 1
Reputation: 45272
It appears that, on the initial render, angular is attempting to evaluate isVisible
for each mentor. Because mentor.isVisible
will be undefined
, the angular framework uses the parent scope's isVisible
.
But then, when you toggle one particular mentor, you effectively assign an isVisible
boolean property to that particular mentor
string. (Because you can happily attach additional properties to a string
in javascript).
After this, you can toggle show/hide all
, and it will affect each mentor that has not had their own isVisible
property assigned to.
It demonstrates how confusing scoping was in the initial version of AngularJS.
Here is a working answer: http://jsfiddle.net/fkw5923t/
I have made some changes:
$scope
, I'm using the controllerName as mnemonic
syntax. In the controller code I assign values to this
rather then $scope
. I prefer this approach, because now the scoping is clear and explicitmentor
from a string to an object, consisting of two values: name
and isVisible
. Again, this now makes the scoping rules clear and explicit.The HTML:
<div ng-controller="MainCtrl as mainCtrl">
<span class = "clickable" ng-click="mainCtrl.isVisible = !mainCtrl.isVisible;" >
(show/hide all)
<i class="fa fa-minus-square-o fa-small"></i>
</span>
<ul ng-show="mainCtrl.isVisible">
<li ng-repeat="mentor in mainCtrl.mentors">
<a class = "clickable" ng-click="mentor.isVisible = !mentor.isVisible;">show/hide</a>
<span ng-show="mentor.isVisible">{{ mentor.name }}</span>
</li>
</ul>
</div>
The javascript:
var app = angular.module('myApp', []);
function MainCtrl() {
this.isVisible = true;
var mentorNames = ['Jonathan', 'Nathan', 'Chris', 'Brian', 'Timothy'];
this.mentors = mentorNames.map(
name => {
return {
name:name,
isVisible:true
};
}
);
}
Upvotes: 1