Conditional Content with ng-repeat in AngularJS - angularjs

I'm trying to build a list of people with dividers for each letter (similar to a phone's address book).
people = ['Angela','Annie','Bob','Chris'];
Result:
A
Angela
Annie
B
Bob
C
Chris
I want to do something similar to this pseudocode using Angular:
<div id="container" ng-init="lastCharacter = ''">
#foreach(person in people)
#if(firstCharacter(person.name) != lastCharacter)
<div class="divider-item">{{firstCharacter(person.name)}}</div>
{{lastCharacter = firstCharacter(person.name)}}
#endif
<div class="person-item">{{person.name}}</div>
#endforeach
</div>
What is the easiest way to achieve this? I couldn't come up with an elegant solution using ng-repeat.

You should create a custom filter and move the grouping logic inside that:
app.filter('groupByFirstLetter',function(){
return function(input){
var result = [];
for(var i = 0;i < input.length; i++){
var currentLetter = input[i][0]
var current = _.find(result, function(value){
return value.key == currentLetter;
});
if(!current){
current = {key: currentLetter, items: []}
result.push(current);
}
current.items.push(input[i]);
}
return result;
};
});
Then the view gets simple:
<div ng-repeat="personGroup in people | groupByFirstLetter">
<div class="divider-item">{{personGroup.key}}</div>
<div class="person-item" ng-repeat="person in personGroup.items">
{{person}}
</div>
</div>
Here's a little working example in plunker : http://plnkr.co/edit/W2qnTw0PVgcQWS6VbyM0?p=preview
It's working but it throws some exceptions, you will get the idea.

try (use sorted list)
<div id="container" ng-repeat="person in people">
<div class="divider-item"
ng-if="$index == 0 || ($index > 0 && firstCharacter(person.name) != firstCharacter(people[$index-1].name))">{{firstCharacter(person.name)}}</div>
<div class="person-item">{{person.name}}</div>
</div>
hope, you have firstCharacter function defined in scope. Or you can simpley use person.name.charAt(0).
edit: as this will contain id container for all person. so best would be using an inner div inside container and run ng-repeat on there
<div id="container" >
<div ng-repeat="person in people">
<div class="divider-item"
ng-if="$index == 0 || ($index > 0 && firstCharacter(person.name) != firstCharacter(people[$index-1].name))">{{firstCharacter(person.name)}}</div>
<div class="person-item">{{person.name}}</div>
</div>
</div>

Related

How to get array count of filtered result on dir-pagination?

At the monent I have this code.
<tr dir-paginate="person in People | filter:q
| orderBy:sortKey:reverse| itemsPerPage: criteria.pagesize"
current-page="currentPage" pagination-id="PeoplePagination">
My question is how to do I get the count for the filtered array when using angular dir-pagination on the controller.
on the directive template url which is dirPagination.tpl.html the code below provides value.
<div class="range-label">Displaying {{ range.lower }} -
{{ range.upper }} of {{ range.total }}</div>
My question is how do I get the {{range.total}} if I put this on my main controller.
UPDATE :
Range is located on dir-pagination directive
link: function dirPaginationControlsLinkFn(scope, element, attrs) {
// rawId is the un-interpolated value of the pagination-id attribute. This is only important when the corresponding dir-paginate directive has
// not yet been linked (e.g. if it is inside an ng-if block), and in that case it prevents this controls directive from assuming that there is
// no corresponding dir-paginate directive and wrongly throwing an exception.
var rawId = attrs.paginationId || DEFAULT_ID;
var paginationId = scope.paginationId || attrs.paginationId || DEFAULT_ID;
if (!paginationService.isRegistered(paginationId) && !paginationService.isRegistered(rawId)) {
var idMessage = (paginationId !== DEFAULT_ID) ? ' (id: ' + paginationId + ') ' : ' ';
throw 'pagination directive: the pagination controls' + idMessage + 'cannot be used without the corresponding pagination directive.';
}
if (!scope.maxSize) { scope.maxSize = 9; }
scope.directionLinks = angular.isDefined(attrs.directionLinks) ? scope.$parent.$eval(attrs.directionLinks) : true;
scope.boundaryLinks = angular.isDefined(attrs.boundaryLinks) ? scope.$parent.$eval(attrs.boundaryLinks) : false;
var paginationRange = Math.max(scope.maxSize, 5);
scope.pages = [];
scope.pagination = {
last: 1,
current: 1
};
scope.range = {
lower: 1,
upper: 1,
total: 1
};
Here's the Plunker
Basically what I want is to get the value of the current array size when the user enter something on the searchbox.
You could use
<li dir-paginate="meal in filteredMeals = (meals | filter:q)
| itemsPerPage: pageSize" current-page="currentPage">
and then reference
<span ng-bind="filteredMeals.length"></span>
as described by https://stackoverflow.com/a/19517533/4068027.
Strangely enough the Angular 1.3+ method with filter: q as filteredMeals did not work with dir-paginate (at least not with the versions used in your plnkr).
See updated plunker
Instead of using dir-paginate - I have created a simple logic, I hope you can use this for your requirement..!!
<script>
var angularPageList = angular.module('angularPageList',['ui.bootstrap']);
angularPageList.controller('listCountControl',['$scope',function($s){
$s.totalListCount = 20;
$s.currentPage = 1;
$s.numPages = 1;
$s.viewby = 5;
$s.itemsPerPage = $s.viewby;
$s.maxSize = 5;
$s.setItemsPerPage = function(num) {
$s.itemsPerPage = num;
$s.currentPage = 1; //reset to first page
}
$s.getLastIndex = function (index){
$s.lastIndex = index;
console.log($s.lastIndex)
}
$s.getFirstIndex = function (index){
$s.firstIndex = index;
console.log($s.firstIndex)
}
}]);
</script>
<div ng-app="angularPageList">
<div class="container">
<h2>List Count - UIB Pagination (AngularJs)</h2>
<div ng-controller="listCountControl">
<div class="row" style="margin-top:50px">
<div class="col-sm-2 text-center">
<div ng-repeat="i in [1,2,3,4,5,6,7,8,9,10,11,12,13,14,15,16,17,18,19,20].slice(((currentPage-1)*itemsPerPage), ((currentPage)*itemsPerPage))">
<span ng-init="$last ? getLastIndex(tutorSearchBlok.sBindex) : '';$first ? getFirstIndex(tutorSearchBlok.sBindex) : ''"></span>
<span ng-init="$last ? getLastIndex(i) : '';$first ? getFirstIndex(i) : ''"></span>
<div style="font-size:18px;padding:10px 0;background:#fafafa;border-top:1px solid #ddd;">{{i}}</div>
</div>
</div>
<div class="col-sm-6">
<span>List Count: </span>«<b>{{firstIndex+1}}</b> to <b>{{lastIndex+1}}</b>» of {{totalListCount}}
<hr>
<ul uib-pagination total-items="totalListCount" ng-model="currentPage" max-size="maxSize" class="pagination-sm" boundary-links="true" num-pages="numPages" items-per-page="itemsPerPage"></ul>
</div>
</div>
</div>
</div>
</div>
<script src="https://code.jquery.com/jquery-3.2.1.js" integrity="sha256-DZAnKJ/6XZ9si04Hgrsxu/8s717jcIzLy3oi35EouyE=" crossorigin="anonymous"></script>
<script src = "https://ajax.googleapis.com/ajax/libs/angularjs/1.5.2/angular.min.js"></script>
<script src="https://maxcdn.bootstrapcdn.com/bootstrap/3.3.7/js/bootstrap.min.js" integrity="sha384-Tc5IQib027qvyjSMfHjOMaLkfuWVxZxUPnCJA7l2mCWNIpG9mGCD8wGNIcPD7Txa" crossorigin="anonymous"></script>
<script src="ui-bootstrap-2.5.0.min.js"></script>

Serial Number using nested NG-Repeat

I want to add serial number in each question
ng-repeat = "Table in Paper"
Display table
ng-repeat = "question in question"
(1) Display question
(2) Display question
I can use $index in Question. But for each Table, it is started from 1.
E.g.
Table 1
Question 1
Question 2
Table 2
Question 1
Question 2
But I want
Table 1
Question 1
Question 2
Table 2
Question 3
Question 4
I use $parent & $index. But not working.
<div ng-repeat="section in Table">
<div ng-repeat="question in section.question">
<span >{{(Section[$parent.$index-1].question.length + $index + 1 )+
'. ' +
question
.Description}}</span>
</div>
try this.
var myApp = angular.module('myApp',[]);
myApp.controller('MyCtrl', ["$scope",function MyCtrl($scope) {
$scope.lastIndex = 0;
$scope.lastIndexes = [];
$scope.Table ={
"table1":
{
"question":["questionA","questionB","questionC","questionD"]
},
"table2":
{
"question":["questionA","questionB","questionC","questionD"]
},
"table3":
{
"question":["questionC","questionD","questionF"]
},
"table4":
{
"question":["questionC","questionD","questionE"]
}
};
$scope.setLastIndex = function(len){
$scope.lastIndex = $scope.lastIndex + len*1;
$scope.lastIndexes.push($scope.lastIndex);
}
}]);
<script src="https://ajax.googleapis.com/ajax/libs/angularjs/1.2.23/angular.min.js"></script>
<div ng-app="myApp" ng-controller="MyCtrl">
<div ng-repeat="(key,value) in Table"
ng-init="setLastIndex(value.question.length)">
{{key}}
<div ng-repeat="question in value.question" >
<span>{{($index+1 +lastIndexes[$parent.$index-1])}} :{{question}}</span>
</div>
</div>
use
<div ng-repeat = "Table in Paper">
<div ng-repeat="q in Table.questions">
{{q}}
</div>
</div>
Here questions is array and present in your json
This may not the perfect solution what I want, But It can be a temporary solution,
Create watch or function in controller to traverse all question
var index=1;
Section.forEach(function (section) {
section.question.forEach(function (question) {
question.Number= index;
index++;
})
})
Then Print this number in ng-repeat
<div ng-repeat="section in Table">
<div ng-repeat="question in section.question">
<span >{{question.Number +'. ' +question.Description}}</span>
</div>
</div>

How can I limit rows showing in an ng-repeat?

I have an ng-repeat that looks like this:
<div ng-repeat="subject in subjects">
<div>{{ subject.name }}</div>
</div>
Is there some way that I can limit the display of rows to rows where subject.id is less than 50 if roleId > 1 and otherwise show all rows?
$scope.roleId = 0 means no filter
$scope.roleId > 0 means filter to show only rows in $scope.subjects where subject.id is less than 50
You can accomplish this using ng-if or use a filter
<div ng-repeat="subject in subjects" ng-if="subject.id>50">
The part about the role id in question is not clear
Use Limit to Filter
<div ng-repeat="subject in subjects | limitTo:quantity">
<div>{{ subject.name }}</div>
</div>
then set the $scope.quantity in your controller based on your business logic.
Use a Custom Filter
to use a custom filter you just set a new $scope function in your controller.
$scope.yourFilter = function (items) {
// probably need some more logic but you get the idea
return items.id < 50;
};
thin in the filter it should look like this
<div ng-repeat="subject in subjects | filter:yourFilter(subjects)">
<div>{{ subject.name }}</div>
</div>
Add a filter
<div ng-repeat="subject in subjects | filter:filterFn">
<div>{{ subject.name }}</div>
</div>
And in your controller, have this function:
$scope.filterFn = function (item) {
if ($scope.roleId > 1) {
return (item.Id < 50) ? true : false;
}
else
return true;
};
If I understood your comments correctly.

Depth of nested ng-repeat — AngularJS

Is there a way to get the depth of a nesting ng-repeats? Say if you are iterating over an nested object of unknown depth like nested comments?
Since ng-repeat creates a new scope, you could do something like this.
{{$depth = $parent.$depth && $parent.$depth + 1 || 1}}
See https://jsfiddle.net/crgt25uk/ for a working example.
var app = angular.module('app',[]);
app.controller('ExampleController',function($scope){
$scope.items = [
{name:'foo',items:[{name:'foofoo'},{name:'foobar'}]},
{name:'bar',items:[{name:'barfoo'},{name:'barbar'}]}
]
})
And your template:
<div ng-app="app">
<div ng-controller="ExampleController">
<li ng-repeat="item in items">{{$depth = $parent.$depth && $parent.$depth + 1 || 1}}: {{item.name}}
<ul>
<li ng-repeat="item in item.items">{{$depth = $parent.$depth && $parent.$depth + 1 || 1}}: {{item.name}}</li>
</ul>
</li>
</div>
</div>

Filter results 6 through 10 of 100 with ng-repeat in AngularJS

I see the limitTo filter in the docs, which allows me to limit the first 5, or last 5 results, but I want to set where my limit starts so I can show the second set of 5 results.
Is there a built in filter for that?
Since Angular 1.4.0, the limitTo filter takes an optional begin argument:
<div ng-repeat="item in items | limitTo:5:5">{{item}}</div>
In older versions, writing a custom filter is fairly straightforward. Here's a naïve implementation based on Array#slice (note you pass the first and last index, instead of a count):
app.filter('slice', function() {
return function(arr, start, end) {
return (arr || []).slice(start, end);
};
});
<div ng-repeat="item in items | slice:6:10">{{item}}</div>
Working jsFiddle: http://jsfiddle.net/BinaryMuse/vQUsS/
Alternatively, you can simply steal the entire Angular 1.4.0 implementation of limitTo:
function limitToFilter() {
return function(input, limit, begin) {
if (Math.abs(Number(limit)) === Infinity) {
limit = Number(limit);
} else {
limit = toInt(limit);
}
if (isNaN(limit)) return input;
if (isNumber(input)) input = input.toString();
if (!isArray(input) && !isString(input)) return input;
begin = (!begin || isNaN(begin)) ? 0 : toInt(begin);
begin = (begin < 0 && begin >= -input.length) ? input.length + begin : begin;
if (limit >= 0) {
return input.slice(begin, begin + limit);
} else {
if (begin === 0) {
return input.slice(limit, input.length);
} else {
return input.slice(Math.max(0, begin + limit), begin);
}
}
};
}
AngularJS provides that functionality already out of the box. If you carefully read the limitTo documentation it allows you to specify a negative value for the limit. That means N elements at the end so if you want to process 5 results after an offset of 5 you need to do the following:
<div ng-repeat="item in items | limitTo: 10 | limitTo: -5">{{item}}</div>
I started playing around with customer filters but then found out you can just call slice inside of the ng-repeat expression:
<div ng-repeat="item in items.slice(6, 10)">{{item}}</div>
As bluescreen said, it can be done using only the limitTo filter, although dealing with the last page problem noticed by Harry Oosterveen needs some extra work.
I.e. using ui-bootstrap pagination directive properties:
ng-model = page // current page
items-per-page = rpp // records per page
total-items = count // total number of records
The expression should be:
<div ng-repeat="item in items | limitTo: rpp * page | limitTo: rpp * page < count ? -rpp : rpp - (rpp * page - count)">{{item}}</div>
Here's a functional example on how to filter a list with offset and limit:
<!doctype html>
<html lang="en" ng-app="phonecatApp">
<head>
<script src="lib/angular/angular.js"></script>
<script src="js/controllers.js"></script>
</head>
<body ng-controller="PhoneListCtrl">
Offset: <input type="text" ng-model="offset" value="0" /><br />
Limit: <input type="text" ng-model="limit" value="10" /><br />
<p>offset limitTo: {{offset - phones.length}}</p>
<p>Partial list:</p>
<ul>
<li ng-repeat="phone in phones | limitTo: offset - phones.length | limitTo: limit">
{{$index}} - {{phone.name}} - {{phone.snippet}}
</li>
</ul>
<hr>
<p>Whole list:</p>
<ul>
<li ng-repeat="phone in phones">
{{$index}} - {{phone.name}} - {{phone.snippet}}
</li>
</ul>
</body>
</html>
The example is based on the second step of AngularJS tutorial.
Note that the filter for the offset has to be put as the first filter if you want to cover the generic case where the limit is uknnown.
from bluescreen answer :
<div ng-repeat="item in items | limitTo: 10 | limitTo: -5">{{item}}</div>
you might also find a scenario where you need to take N-th item till the last item, this is another way using limitTo:
<div ng-repeat="item in items | limitTo:-(items.length-5)>{{item}}</div>
negative sign will take as much as items.length from last, then minus 5 inside the bracket will skip the first 5 items
There's a tidier way to do it without having to invoke a filter and will behave more like a paginator:
$scope.page = 0;
$scope.items = [ "a", "b", "c", "d", "e", "f", "g" ];
$scope.itemsLimit = 5;
$scope.itemsPaginated = function () {
var currentPageIndex = $scope.page * $scope.itemsLimit;
return $scope.items.slice(
currentPageIndex,
currentPageIndex + $scope.itemsLimit);
};
And just stick that in your view:
<ul>
<li ng-repeat="item in itemsPaginated() | limitTo:itemsLimit">{{item}}</li>
</ul>
Then you can just increment/decrement $scope.page:
$scope.page++;
You can also create your own reusable filter startFrom like so:
myApp.filter('startFrom', function () {
return function (input, start) {
start = +start;
return input.slice(start);
}
});
and then use it in your ng-repeat like so:
ng-repeat="item in items | startFrom: 5 | limitTo:5
That way it is done "in a spirit of AngularJs filters", testable, can have some "extra" logic if needed, etc.
#bluescreen's ans and #Brandon's ans are not equivalent.
for example:
var pageSize = 3;
var page = 2;
var items = [1, 2, 3, 4];
--
item in items | limitTo: page*pageSize | limitTo: -1*pageSize
produce [2, 3, 4]
item in items | slice: (page-1)*pageSize : page*pageSize
produce [4]
For Ionic based projects find the solution below:
mainpage.html:
<ion-view view-title="My Music">
<ion-content>
<ion-list>
<ion-item ng-repeat="track in tracks | limitTo: limit | limitTo: -10 ">
{{track.title}}
</ion-item>
</ion-list>
<div class="list"></div>
<div align="center">
<button class="button button-small button-positive" ng-disabled="currentPage == 0" ng-click="decrementLimit()">
Back
</button>
<button class="button button-small button-energized">
{{currentPage+1}}/{{numberOfPages()}}
</button>
<button class="button button-small button-positive" ng-disabled="currentPage >= data_length/pageSize - 1" ng-click="incrementLimit()">
Next
</button>
</div>
</ion-content>
</ion-view>
controller.js:
var myApp = angular.module('musicApp.controllers', []);
myApp.controller('AppCtrl', function($scope, $http) {
console.log('AppCtrl called');
// $scope.tracks = MusicService.query();
$http.get('api/to/be/called').then(function(result){
$scope.tracks = result.data['results'];
$scope.data_length = $scope.tracks.length;
console.log(result)
// console.log($sco pe.tracks.length)
});
var currentPage = 0;
$scope.pageSize = 10;
$scope.numberOfPages = function(){
return Math.ceil($scope.tracks.length/$scope.pageSize);
}
var limitStep = 10;
$scope.limit = limitStep;
$scope.currentPage = currentPage;
$scope.incrementLimit = function(){
$scope.limit += limitStep;
$scope.currentPage += 1
};
$scope.decrementLimit = function(){
$scope.limit -= limitStep;
$scope.currentPage -= 1
}
});

Resources