2

I have an ng-repeat directive where i show all the objects (ideas). If an idea description (string) is longer than x, i want to display only the first x charachters and a "show all" link. User can click on this link, and the whole text will be displayed. But only one idea at a time can be displayed with its whole text.

I have this for now:

div(ng-show = "idea.description.length > maxIdeaDescLength && openLongIdea != idea._id")
  i {{idea.description.substring(0, maxIdeaDescLength) }} ...
  a(href='', ng-click='openLongIdea = idea._id') show all
div(ng-show = "idea.description.length <= maxIdeaDescLength || openLongIdea == idea._id")
  i {{idea.description}}

This is a part of my controller:

$scope.openLongIdea = 0;

So when i click on the show all link, the ideaID will be saved to the variable openLongIdea. And because of my ng-show conditions i expect to display whole description only when idea-ID matches with openLongIdea-ID. But i still see more than one ideas with their long descriptions at a time.

First time when the ideas are displayed, my logic works. When i click on a show all link, the longer text will be displayed. But when i click the see all link of another idea, it will also be displayed as a whole beside the old idea, although i overwrite the value in openLongIdea with the new Idea-ID.

What is the problem here?

akcasoy
  • 6,497
  • 13
  • 56
  • 100
  • 1
    could you provide the code for controller as well? It might be that you bind the scope to a primitive vars. – Lauwrentius Oct 16 '15 at 20:54
  • 1
    javascript objects can be weird, so it's recommended in angular to "always use a dot". That is, access properties of objects instead of using primitives. Access something like `formObject.openLongIdea` instead of just `openLongIdea`. That's resolved random problems for me in the past. – ryanyuyu Oct 16 '15 at 20:56
  • I have edited my question. This is how i define the openLongidea: $scope.openLongIdea = 0; And i do not have a formObject. The list is readonly. – akcasoy Oct 16 '15 at 21:00

2 Answers2

3

As noted in the comments, it looks like an issue with binding to a primitive. In JavaScript, primitives (booleans, numbers, strings) are immutable, so when you change one, such as your number maxIdeaDescLength, the previous instance is discarded and a new one is used. This breaks Angular's two-way binding and any other usages of maxIdeaDescLength are not updated with the new value.

You could address this issue by making openLongIdea a property on an object on $scope e.g. $scope.data.openLongIdea. In that case the $scope has a reference to data even if maxIdeaDescLength changes, so the updated value of maxIdeaDescLength can be accessed.

However, consider switching to the controllerAs View Syntax. From John Papa's style guide, among other arguments:

It promotes the use of binding to a "dotted" object in the View (e.g. customer.name instead of name), which is more contextual, easier to read, and avoids any reference issues that may occur without "dotting".

Updated controller:

var app = angular.module("app", []);
app.controller('ctrl', function () {
  var vm = this;
  vm.maxIdeaDescLength = 10;
  vm.ideas = [
    {_id : 0, description :'abcd efgh ijkl'},
    {_id : 1, description :'qwer tyui opzx'}
  ];
});

Example view:

<div ng-app="app" ng-controller="ctrl as vm">
  <div ng-repeat="idea in vm.ideas">
    <div ng-show = "idea.description.length > vm.maxIdeaDescLength && vm.openLongIdea !== idea._id">
      {{idea.description.substring(0, vm.maxIdeaDescLength) }} 
      <a href='' ng-click='vm.openLongIdea = idea._id'> show all </a>
    </div>
    <div ng-show = "idea.description.length <= vm.maxIdeaDescLength || vm.openLongIdea === idea._id">
      {{idea.description}}
    </div>
  </div>
</div>

JsFiddle

sheilak
  • 5,833
  • 7
  • 34
  • 43
  • since my controller definitions are in another file, i have to update more than one files.. i will get you back as soon as i try this approach. thank you. – akcasoy Oct 16 '15 at 21:49
  • Thank you! It works. But can you explain why the regular angular approach has this "issue with binding to a primitive" problem – akcasoy Oct 16 '15 at 21:57
  • 1
    Updated answer there with more details, I hope that helps – sheilak Oct 16 '15 at 22:09
1

I would create a custom filter directive for this because it will be more readable. The recommendation of controllerAs in the other answer is very good I've missed this point in my demo.

Please have a look at the demo below or in this fiddle.

Collapsing/Expanding is possible in the demo by clicking on the paragraph.

The directive could be improved at one point if you toggle each single idea it's possible that you need to click show/hide all button twice if the current globalDisplay isn't matching.

For the 'dot.rule' please have a look at this SO question.

angular.module('demoApp', [])
 .constant('appConst', {
     maxIdeaDescLength: 50,
     expandedDefault: false
 })
 .filter('descLimit', descLimitFilter)
 .controller('MainController', MainController);

function descLimitFilter($filter) {
 return function(input, len, expanded, ellipsesChars) {
        var ellipses = ellipsesChars || '...';
     return expanded? input : $filter('limitTo')(input, len) + ellipses;
    }
}

function MainController($scope, $http, appConst) {
    $scope.maxIdeaDescLength = appConst.maxIdeaDescLength;
    $scope.toggle = toggle;
    $scope.toggleAll = toggleAll;
    
    var globalDisplay = appConst.expandedDefault; // hide all by default;
    
    activate();
    
 function activate() {
        $http.get('https://demo5147591.mockable.io/ideas')
            .then(function(response) {
                $scope.ideas = response.data;
                //console.log(response.data);
             setExpandedAll($scope.ideas, globalDisplay);
        });
    }
    
    function toggle(item) {
     item.expanded = !item.expanded;
    }
    
    function toggleAll(items) {
        globalDisplay = !globalDisplay;
     setExpandedAll(items, globalDisplay);
    }
    
    function setExpandedAll(items, state) {
     angular.forEach(items, function(item) {
         item.expanded = state;
        });
    }
}
<script src="https://ajax.googleapis.com/ajax/libs/angularjs/1.2.23/angular.min.js"></script>
<div ng-app="demoApp" ng-controller="MainController">
    <button ng-click="toggleAll(ideas)">show/hide descriptions</button>
    <div>
        <div ng-repeat="idea in ideas">
            <h2>{{idea.title}}</h2>
            <p ng-click="toggle(idea)">{{idea.description | descLimit: maxIdeaDescLength : idea.expanded}}</p>
        </div>
    </div>
</div>
Community
  • 1
  • 1
AWolf
  • 8,770
  • 5
  • 33
  • 39