Divider into ng-repeat for random number of iterations - angularjs

I need to create an ng-repeat iteration with divider in it. The problem is that I have to add divider after every two, three or four elements randomly. I started with this code:
<li repeat-start="person in persons track by $index">
<p>{{ person.name }}</p>
</li>
<li ng-repeat-end ng-if="($index + 1) % 2 === 0">
<p>--divider--</p>
</li>
It works great when I specify the exact value of 2 inside ng-if. It shows me result like that.
Person 1
Person 2
--divider--
Person 3
Person 4
--divider--
Person 5
etc...
But how to specify this coefficient dynamically? I added a function to genereate a random number among 2, 3 and 4.
$scope.getNumber = function() {
return Math.floor(Math.random() * (3) + 2);
}
But when I try to change hardcoded value I cannot see the desirable result. None of these solutions works. Neigher with simple function call.
ng-if="($index + 1) % getNumber() === 0"
nor with ng-init variations.
<li repeat-start="person in persons track by $index" ng-init="coeff = getNumber()">
<p>{{ person.name }}</p>
</li>
<li ng-repeat-end ng-if="($index + 1) % coeff === 0">
<p>--divider--</p>
</li>
How to achieve this functionality?

The problem was with Infinite $digest Loop, which was caused by fact, that getNumber function returns random results, so AngularJS can't stabilize itself. To calculate getNumber only once for each ng-repeat iteration, you can use ng-init directive, as you already did (may be your code doesn't work due to typo: repeat-start instead of ng-repeat-start):
angular.module('app', []).controller('ctrl', ['$scope', function($scope) {
$scope.persons = [];
for(var i = 0; i < 20; i++)
$scope.persons.push({name:'Person ' + i});
$scope.getNumber = function() {
return Math.floor(Math.random() * 3 + 2);
}
}])
<script src="https://ajax.googleapis.com/ajax/libs/angularjs/1.2.23/angular.min.js"></script>
<ul ng-app='app' ng-controller='ctrl'>
<li ng-init='temp = getNumber()' ng-repeat-start="person in persons">
<p>{{person.name}}</p>
</li>
<li ng-repeat-end ng-if="($index + 1) % temp === 0">
<p>--divider--</p>
</li>
</ul>
Another solution is to "cache" results of getNumber function, for example at person entity:
angular.module('app', []).controller('ctrl', ['$scope', function($scope) {
$scope.persons = [];
for(var i = 0; i < 20; i++)
$scope.persons.push({name:'Person ' + i});
$scope.getNumber = function(person) {
return person.temp || (person.temp = Math.floor(Math.random() * 3 + 2));
}
}])
<script src="https://ajax.googleapis.com/ajax/libs/angularjs/1.2.23/angular.min.js"></script>
<ul ng-app='app' ng-controller='ctrl'>
<li ng-repeat-start="person in persons">
<p>{{person.name}}</p>
</li>
<li ng-repeat-end ng-if="($index + 1) % getNumber(person) === 0">
<p>--divider--</p>
</li>
</ul>

Try this (some refactorings):
<li repeat-start="person in persons track by $index">
<p>{{ person.name }}</p>
<p ng-if="putDividerHere($index)">--divider--</p>
</li>
$scope.putDividerHere(index) {
var randomNumber = Math.floor(Math.random() * (3) + 2);
return ((index + 1) % randomNumber) === 0;
}

A small change needed. Call a function in your ng-if that cals the getNumber function and returns the condition true/false
HTML:
<li ng-repeat-end ng-if="isShowDivider($index+1)">
<p>--divider--</p>
</li>
JS:
$scope.isShowDivider = function (index) {
if (index % $scope.getNumber() !== 0)
return false;
return true;
}
$scope.getNumber = function() {
return Math.floor(Math.random() * (3) + 2);
}
Edit for the iterations reached. Aborting error.
<li ng-repeat="person in persons track by $index">
<p>{{person.name}}</p>
<p ng-if="isShowDivider($index+1)">--divider--</p>
</li>

Related

iteration scope name in ng-repeat

This is my controller
var i;
for (i = 1; i <= 3; i++ ) {
$scope["items"+i].push({
notification: item.notification,
admin_id: item.admin_id,
user_id: item.user_id,
chat_time: item.chat_time
})
}
This is my static scope in ng-repeat
<ul>
<li ng-repeat="x in items1"></li>
<li ng-repeat="x in items2"></li>
<li ng-repeat="x in items3"></li>
</ul>
How to make dynamic iteration scope variabel in ng-repeat in single ng-repeat like this
<ul>
<li ng-repeat="x in items[i++]">
<!-- generate from here-->
<li ng-repeat="x in items1"></li>
<li ng-repeat="x in items2"></li>
<li ng-repeat="x in items3"></li>
<!-- to here -->
</li>
</ul>
and it will display like my static scope
Thanks , appreciate ur help and comment
You need to store all the different arrays in a new Array and then, you can loop it around as shown (I have created a sample):
JS:
$scope.item1 = [ /* json array 1 */];
$scope.item2 = [ /* json array 2 */];
$scope.item3 = [ /* json array 3 */];
$scope.itemsList = [];
for (i = 0; i < 3; i++) {
var varName = '$scope.item' + (i + 1);
$scope.itemsList.push(eval(varName));
}
HTML:
<li ng-repeat="mainList in itemsList">
<p ng-repeat="specificList in mainList">
<span>Id: {{specificList.id}}</span>
<br />
<span>Name: {{specificList.name}}</span>
</p>
</li>
Have a look at the demo.

Add item in array repeatedly for ng-repeat

I have an array of objects which looks like:
{
id: "1234",
general: {
title: "lorem",
body: "..."
}
}, ...
This data is being shown with an ng-repeat:
<ul>
<li ng-repeat="item in items track by $index">
<h2>{{item.general.title}}</h2>
<p>{{item.general.body}}</p>
</li>
</ul>
Now what I want to achieve is to add items to this list. Every 15 items I want to add a new item to the array to display in my ng-repeat. The item has a different structure:
<li>
<p>a text</p>
<a>a link</a>
</li>
So far I got this in my controller:
var addLinks = function addLinks(interval, array) {
var newArray = array.slice();
for(var i = interval - 1; i < array.length; i += interval) {
newArray.splice(i, 0, {
// Here comes the item to add
});
}
return newArray;
};
$scope.items = addLinks(15, articleService.articles);
My question is how do I add the item without just copying the html?
You could use ng-repeat-start and ng-repeat-end and only add the other element if you are on $index+1 % 15, like this:
<ul>
<li ng-repeat-start="item in vm.array track by $index">{{item.a}}</li>
<li ng-repeat-end ng-if="$index>0 && $index+1 % 15 == 0"></li>
</ul>
Here a plunkr: http://plnkr.co/edit/qwkXGcBcxlDy0hNHTvAo?p=preview
I would add additional properties to the item in question:
var addLinks = function addLinks(interval, array) {
//don't return a new array!
//var newArray = array.slice();
for(var i = interval - 1; i < array.length; i += interval) {
array[i].text = "foo";
array[i].link = "bar";
}
return array;
};
And in your view:
<ul>
<li ng-repeat="item in items track by $index">
<h2>{{item.general.title}}</h2>
<p>{{item.general.body}}</p>
<p ng-show="item.text">item.text</p>
<p ng-show="item.link">item.link</p>
</li>
</ul>

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>

AngularJS Items selection and DOM manipulation

I have a list of items populated from a JSON, then i need to pick their values and populate another list of selected items, but, in case the particular item is already selected, add one more to count.
app.controller("NewPizza", function($scope, ingredients) {
$scope.selectedIngredients = [];
ingredients.get().then(function(response){
$scope.ingredients = response.data;
});
function _ingredientExist(id) {
return $scope.selectedIngredients.some(function(el) {
return el._id === id;
});
}
function _addMore(selectedIngredient) {
console.log(selectedIngredient)
}
$scope.addIngredient = function(selectedIngredient) {
if($scope.selectedIngredients.length == 0) {
$scope.selectedIngredients.push(selectedIngredient);
}else{
if(_ingredientExist(selectedIngredient._id)) {
_addMore(selectedIngredient._id);
return false;
}
$scope.selectedIngredients.push(selectedIngredient);
}
};
});
The result should be like this
Items to select
Cheese
Bacon
ham
Items selected
2 Cheese (In case user select cheese multiple times)
Bacon
HTML
<div class="">
<h1>New Pizza</h1>
<input ng-model="name"/>
<a>Save pizza</a>
<ul >
<li class="selectIngredients" ng-repeat="ingredient in ingredients" ng-click="addIngredient(ingredient)" id="{{ingredient._id}}">
{{ingredient.name}}
</li>
</ul>
<ul ng-model="selectedIngredients">
<li data-id="{{selectedIngredient._id}}" ng-repeat="selectedIngredient in selectedIngredients track by $index">
<span>1</span> {{selectedIngredient.name}}
</li>
</ul>
</div>
The problem is i dont know how exactly approach this feature because inside a controller DOM manipulation is considered a bad practice, but if i make a directive to deal with i dont know how to populate $scope.selectedIngredients properly.
Thanks!!
One way is that you can add a count to your items model, then copy that model and increment the number.
Created a fiddle: http://jsfiddle.net/ztnep7ay/
JS
var app = angular.module('itemsApp',[]);
app.controller('ItemsCtrl',function($scope) {
$scope.items = [
{name:'Cheese',num:0},
{name:'Bacon',num:0},
{name:'Ham',num:0}
];
$scope.my_items = angular.copy($scope.items);
$scope.addItem = function(item) {
var idx = $scope.items.indexOf(item);
var num = $scope.my_items[idx].num;
$scope.my_items[idx].num = num + 1;
};
$scope.removeItem = function(my_item) {
var idx = $scope.my_items.indexOf(my_item);
var num = my_item.num;
if (num > 0) {
$scope.my_items[idx].num = num -1;
}
};
});
HTML
<div ng-app="itemsApp" ng-controller="ItemsCtrl">
<h4>Available Items</h4>
<table>
<tr ng-repeat="i in items">
<td>{{i.name}}</td>
<td><button ng-click="addItem(i)">+</button></td>
</tr>
</table>
<hr>
<h4>My Items</h4>
<table>
<tr ng-repeat="i in my_items" ng-show="i.num > 0">
<td>{{i.name}} ({{i.num}})</td>
<td><button ng-click="removeItem(i)">Remove 1</button></td>
</tr>
</table>
</div>
You are right that it is considered wrong to update the DOM from a controller in the Angular world.
The reason for that is because you don't need to -- if you update your data - for example, the selectedIngredients array -- angular will update the DOM for you.
One way to accomplish this is to keep track of the count of each ingredient as well as what ingredient was added. You can do this without touching the Ingredient json that you get back from the server.
Then, when you change the count, angular will update the DOM for you.
Here's am example: Live Plnkr Example
HTML
<!DOCTYPE html>
<html ng-app="test">
<head>
<script data-require="angular.js#1.3.0-beta.5" data-semver="1.3.0-beta.5" src="https://code.angularjs.org/1.3.0-beta.5/angular.js"></script>
<link rel="stylesheet" href="style.css" />
<script src="script.js"></script>
</head>
<body ng-controller="PizzaCtrl">
<h4>Toppings</h4>
<ul>
<li ng-repeat="i in ingredients">
{{i.name}} - <a href ng-click="addIngredient(i)">Add</a>
</li>
</ul>
<h4>Selected Toppings</h4>
<ul>
<li ng-repeat="t in toppings">
{{t.ingredient.name}}
<span ng-if="t.count > 1">
x{{t.count}}
</span>
<a href ng-click="removeTopping(t, $index)">Remove</a>
</li>
</ul>
</body>
</html>
JS
angular.module('test', [])
.controller('PizzaCtrl', function($scope) {
/* let's protend we called ingredients.get() here */
$scope.ingredients = [
{ name: 'Cheese', id: 1 },
{ name: 'Bacon', id: 2 },
{ name: 'Ham', id: 3 }
];
/* this will hold both an Ingredient and a Count */
$scope.toppings = [];
/* Check if the ingredient already exists in toppings[], and if so,
* return it. */
function findTopping(ingredient) {
for(var i = 0; i < $scope.toppings.length; ++i) {
if ($scope.toppings[i].ingredient.id == ingredient.id) {
return $scope.toppings[i];
}
}
return null;
}
/* If the ingredient is already there, increment it's count,
* otherwise add it. */
$scope.addIngredient = function(ingredient) {
var t = findTopping(ingredient);
if (t) {
t.count++;
} else {
$scope.toppings.push({ingredient: ingredient, count: 1});
}
};
/* Opposite of the above! */
$scope.removeTopping = function(t, index) {
if (t.count > 1) {
t.count--;
} else {
$scope.toppings.splice(index, 1);
}
};
})

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