Reputation: 7215
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?
Upvotes: 2
Views: 1010
Reputation: 8980
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>
Upvotes: 1
Reputation: 5873
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>
Upvotes: 3