e-barnett
e-barnett

Reputation: 75

AngularJS: ng-repeat and scoped ng-click

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

Answers (3)

e-barnett
e-barnett

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

Alexander Elgin
Alexander Elgin

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

Andrew Shepherd
Andrew Shepherd

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:

  • Rather than assign values to $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 explicit
  • I've changed mentor 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

Related Questions