AngularJs: loop through number - angularjs

I'm looping a number (from 0 to 7) to get the index of the next day.
Here bellow is a fiddle working.
The problem is the first day is not "Monday", but Friday. So, the number is not 0 but 4...
I do not understand where is the problem.
Please help
(function(){
var app = angular.module('myApp', [ ]);
app.controller('CalenderController', function(){
this.firstDay = -1;
this.getDayName = function(){
this.firstDay++;
if(this.firstDay == 7){
this.firstDay = 0;
}
return dayNames[this.firstDay];
};
this.dayLength = function(){
return new Array(13);
}
});
//Variables
var dayNames = ['Mo', 'Di', 'Mi', 'Do', 'Fr', 'Sa', 'So'];
})();
<script src="https://ajax.googleapis.com/ajax/libs/angularjs/1.2.0/angular.min.js"></script>
<div class="container" ng-app="myApp" ng-controller="CalenderController as calender">
<div ng-repeat="item in calender.dayLength() track by $index">
{{calender.getDayName()}}
</div>
</div>

It is a very bad idea to leave side-effects in functions that are being watched by Angular. Any function that is called from within an expression {{something()}} will be evaluated on every digest cycle, and so, these functions must be idempotent.
The getDayName function is not idempotent, because it changes this.firstDay.
Not only that, but it also returns a different value every time it's called, and so it causes the digest cycle to re-run (until it's aborted by Angular after 10 iterations).
Instead, use the $index directly to access the dayName array:
<div ng-repeat="item in calendar.dayLength()">
{{calendar.dayNames[$index % 7]}}
</div>
and expose dayNames as a VM with this.dayNames.
EDIT: On second thought, it's better to expose this as a function, so that you could do mod 7 there:
$scope.getDayName = function(dayIndex){
return dayNames[dayIndex % 7];
}
and in the View:
{{calendar.getDayName($index)}}
EDIT 2: If you don't need to have a flat DOM hierarchy of <div>s for all the days over 2 weeks, you could even do this much simpler:
<div ng-repeat="week in [0, 1]">
<div ng-repeat="day in dayNames">
{{day}}
</div>
</div>

Related

Function in ng-bind , multiple call?

I couldnt fint the answer on stack or google...
Why function in ng-bind call many times ?
html:
<li ng-if="byProviders" ng-repeat="(key, value) in byProviderGames | groupBy: 'provider'">
<p ng-bind="providersNames(key)"></p>
</li>
controller:
$scope.providersNames = function providersNames(key) {
// providersObject's length is 8
var index = $scope.providersObject.findIndex(function(x){ return x.name == key });
// Call more then 1000 times
console.log($scope.gamesProviders[index]);
var title = $scope.providersObject[index].title;
return title;
}
From the ngBind source code we can see that this directive registers a ngBindWatchAction callback to change element.textContent whenever the ng-bind attribute changes using scope.$watch:
scope.$watch(attr.ngBind, function ngBindWatchAction(value) {
element.textContent = stringify(value);
});
The watchExpression that is used as a first argument of the scope.$watch method is called on every call to $digest() (at least 1 time) and returns the value that will be watched (but it may be executed multiple times to check if the model was not changed by by other watchers). This is mentioned in the docs:
Be prepared for multiple calls to your watchExpression because it will
execute multiple times in a single $digest cycle if a change is
detected.
In this example $scope.getName method will be called 11 times until its returned value become stable:
angular.module('bindExample', [])
.controller('ExampleController', ['$scope', function($scope) {
$scope.name = 'Arik';
$scope.count = 0;
$scope.getName = function() {
if ($scope.count < 10) { $scope.count++; }
console.log($scope.count);
return $scope.name + $scope.count;
};
}]);
<script src="//ajax.googleapis.com/ajax/libs/angularjs/1.6.4/angular.min.js"></script>
<body ng-app="bindExample">
<div ng-controller="ExampleController">
Hello <span ng-bind="getName()"></span>!
</div>
</body>
There are a couple things you can do to help with performance and how it affects the number of times the function is called.
First, you can add a track by $index statement to the end of the ng-repeat. It would end up looking like this.
ng-repeat="(key, value) in byProviderGames | groupBy: 'provider' track by $index"
Second, if just displaying data and no other interaction will take place you can unbind them from the scope. So it'll no longer dirty check for changes. It'll end up looking like this.
ng-repeat="(key, value) in ::byProviderGames | groupBy: 'provider'"
Superpower it with both.
ng-repeat="(key, value) in ::byProviderGames | groupBy: 'provider' track by $index"
You can even go as far as to unbind the actual value.
<p ng-bind="::providersNames(key)"></p>
I'm including a code pen, so you can see how many times the function is called. Nowhere as much as you indicated above. Hope it helps. CodePen
Unbind, properly known by (one-time-binding)[https://docs.angularjs.org/guide/expression]
One-time binding
An expression that starts with :: is considered a one-time expression. One-time expressions will stop recalculating once they are stable, which happens after the first digest if the expression result is a non-undefined value (see value stabilization algorithm below).
function exampleController($scope) {
$scope.exampleArray = [1, 2, 3, 4];
var exampleLookupArray = [
{ name: "Schnauzer" },
{ name: "Dachshund" },
{ name: "German Shepard" },
{ name: "Doberman Pinscher" }
];
$scope.breedLookUp = function(key) {
console.count();
return exampleLookupArray[key-1].name;
};
}
angular
.module("example", [])
.controller("exampleController", exampleController);
<script src="https://ajax.googleapis.com/ajax/libs/angularjs/1.5.8/angular.min.js"></script>
<div class="container-fluid" ng-app="example">
<div class="container" ng-controller="exampleController">
<div ng-repeat="ex in ::exampleArray track by $index">
<span ng-bind="::breedLookUp(ex)"></span>
</div>
</div>
</div>

angular Infinite $digest Loop in ng-repeat

I want to call function in ng-repeat attribute, here is my code
example plnkr
html
<body ng-controller="mainCtrl">
<div ng-repeat='item in getGroupedRange() track by item.id'>
<span>{{item.val}}</span>
<span>{{item.abs}}</span>
<span>{{item.rel}}</span>
<span>{{item.cum}}</span>
</div>
</body>
js
$scope.getGroupedRange = function() {
return [
{
val: 1,
abs: 1,
rel: 1,
cum: 1,
id: 123456
}
];
};
When I opened console I noticed the error
10 $digest() iterations reached. Aborting!
Watchers fired in the last 5 iterations: [[{"msg":"fn: function (c,d,e,f){e=a(c,d,e,f);return b(e,c,d)}","newVal":9,"oldVal":8}],[{"msg":"fn: function (c,d,e,f){e=a(c,d,e,f);return b(e,c,d)}","newVal":10,"oldVal":9}],[{"msg":"fn: function (c,d,e,f){e=a(c,d,e,f);return b(e,c,d)}","newVal":11,"oldVal":10}],[{"msg":"fn: function (c,d,e,f){e=a(c,d,e,f);return b(e,c,d)}","newVal":12,"oldVal":11}],[{"msg":"fn: function (c,d,e,f){e=a(c,d,e,f);return b(e,c,d)}","newVal":13,"oldVal":12}]]
The main goal of my code is using function in ng-repeat for calculating data in each event loop
No, you can't use function in ngRepeat like this. The problem is that Angular uses strict comparison of objects in digest loop to determine if the value of the property changed since the last check. So what happens is that getGroupedRange returns new value (new array) each time it's called. Angular has no idea and considers this value as unstable and thus continues checking. But it aborts after 10 checks.
You need to construct necessary array and assign it to scope property, so it will not change during digest loop:
$scope.groupedRange = $scope.getGroupedRange();
then use it like in ngRepeat
<div ng-repeat='item in groupedRange track by item.id'>
<span>{{item.val}}</span>
<span>{{item.abs}}</span>
<span>{{item.rel}}</span>
<span>{{item.cum}}</span>
</div>
Angular will do calculation and automatic showing in template on data change for you. But by putting ng-repeat='item in getGroupedRange() you put this into endles cycle recalculation.
Try to avoid this by assigning the value of the items (that may be changed by $scope.getGroupedRange function in any way) in the list to some scope variable, say $scope.range, that will be iterated in ng-repeat.
in controller
$scope.getGroupedRange = function() {
$scope.range = [
{
val: 1,
abs: 1,
rel: 1,
cum: 1,
id: 123456
}
];
};
in template
<div ng-repeat='item in range track by item.id'>
<span>{{item.val}}</span>
<span>{{item.abs}}</span>
<span>{{item.rel}}</span>
<span>{{item.cum}}</span>
</div>
Found the solution:
$scope.prev = null;
$scope.getGroupedRange = function() {
var data = [{
val: 1,
abs: 1,
rel: 1,
cum: 1,
id: 123456
}];
if (angular.equals($scope.prev, data)) {
return $scope.prev;
}
$scope.prev = data;
return data;
};
Try doing something like this:
<body ng-controller="mainCtrl">
<div ng-init="grpRange = getGroupedRange()">
<div ng-repeat='item in grpRange track by item.id'>
<span>{{item.val}}</span>
<span>{{item.abs}}</span>
<span>{{item.rel}}</span>
<span>{{item.cum}}</span>
</div>
</div>
</body>

angularjs infinite digest errors when looping through a list

Why does the following give the error:
Error: [$rootScope:infdig] 10 $digest() iterations reached. Aborting!
Code
<div ng-app>
<h2>Todo</h2>
<div ng-controller="TodoCtrl">
<span ng-bind="getText()"></span>
</div>
</div>
function TodoCtrl($scope) {
$scope.todos = [
{text:'learn angular', done:true},
{text:'build an angular app', done:false}];
$scope.getText = function() {
var names = $scope.todos.map(function(t) {
return t.text;
});
return names;
}
};
The code block is supposed to grab all todos and then render their names in a list using ng-bind. It works, but tons of digest iteration errors show up in console.
jsfiddle
It is really a bad practice to use a function evaluation in ng-bind, reason for this infinite digest cycle is because your digest cycle never gets settled. Everytime digest cycle happens ng-bind expression also runs and since the return value from ng-bind expression is always different (different object reference produced by array.map) it has to rerun the digest cycle again and it goes on until reached the max limit set, i.e 10.
In your specific case you could just set the names as a scope property and ng-bind="name".
$scope.names = $scope.todos.map(function(t) {
return t.text;
}).join();
As a general rule you can make sure you update the property name only when needed from your controller, example when an event occurs like adding a todo, removing a todo etc.. A typical scenario in this answer. You could also use interpolation instead of ng-bind and use function expression. {{}}. ie:
$scope.getText = function() {
return $scope.todos.map(function(t) {
return t.text;
}).join();
}
and
<span>{{getText()}}</span> <!--or even <span ng-bind="getText()"></span>-->
Fiddle
I feel like you have over complicated this i have updated the fiddle with a working solution http://jsfiddle.net/U3pVM/12417/.
<div ng-app>
<h2>Todo</h2>
<div ng-controller="TodoCtrl">
<div ng-repeat="todo in todos">
<span >{{ todo.text}}</span>
</div>
</div>
</div>
function TodoCtrl($scope) {
$scope.todos = [
{text:'learn angular', done:true},
{text:'build an angular app', done:false}];
};

Adding class to an element on its click event

I am new to Angular Js. I need to add a class to an element on its click event. I tried the following code. But it is not working.
<html>
<head>
<style>
.active{color:red;}
</style>
<script src="js/lib/jquery.min.js"></script>
<script src="js/lib/angular.min.js"></script>
</head>
<body ng-app="MyApp" ng-controller="MyController">
<div ng-repeat="data in datas">
<p ng-click='selectMe()'>{{data.name}}</p>
</div>
<script>
var app = angular.module('MyApp',[]);
app.controller('MyController',function($scope){
$scope.datas = [{name:"first"},{name:"second"}];
$scope.selectMe = function (){
$(this).addClass('active');
}
});
</script>
</body>
</html>
What is the problem in this code? Is it necessary to use ng-class ? How to do it?
You can pass $event to click
<p ng-click='selectMe($event)'>{{data.name}}</p>
//code:
$scope.selectMe = function (event){
$(event.target).addClass('active');
}
The Angular way (MVVM actually) to do this is to update the model through the click event and then paint according to the model, e.g.:
app.controller('MyController',function($scope){
$scope.datas = [{name:"first", selected:false},{name:"second",selected:false}];
$scope.selectMe = function(data) {
var i;
for( i=0; i < $scope.datas.length; i++ ) {
$scope.datas[i].selected = false;
}
data.selected = true;
};
}
And the HTML:
<div ng-repeat="data in datas">
<p ng-click='selectMe(data)' ng-class="{selected: data.selected}>{{data.name}}</p>
</div>
I know this question already has an answer, but I was shocked when I realized that the chosen best answer (which I had already implemented in some of my code) doesn't align with the AngularJS documentation.
According to AngularJS Documentation:
Do not use controllers to:
Manipulate DOM — Controllers should contain only business logic. Putting any presentation logic into Controllers significantly affects its testability. Angular has databinding for most cases and directives to encapsulate manual DOM manipulation.
huston007's answer works great, however, it does not follow this recommendation.
With this as your data input:
$scope.peeps = {
'0': {
'id': 0,
'first_name': 'Tony',
'last_name': 'Martin'
},
'1': {
'id': 1,
'first_name': 'Gerald',
'last_name': 'Johanssen'
},
'2': {
'id': 2,
'first_name': 'Bobby',
'last_name': 'Talksalot'
}
};
And this your html:
<ul>
<li ng-repeat="peep in peeps"
ng-click="addOrRemoveClassFromMe(peep.id)"
ng-class="{selected: selectedPeeps[peep.id]}">
{{peep.first_name}} {{peep.last_name}}
</li>
</ul>
My suggested solution uses an array of objects with the person's id as the key and a booleon as the value. This is linked to in the DOM through the ngClass directive.
//-- create the selected peeps array
$scope.selectedPeeps = {};
//-- add the id to an array with a true-ey value
$scope.addOrRemoveClassFromMe = function(id) {
//-- Set selected peeps as true/false
if($scope.selectedPeeps[id]) {
$scope.selectedPeeps[id] = false;
} else {
$scope.selectedPeeps[id] = true;
}
};
You can check out my codepen here.
I also have another version that removes all of this logic from the controller, leaving only the variable definition, but I thought that might be out of the scope of this question. (codepen.io/joshwhatk/pen/bwmid)
Hope this helps.
I know this is an old question but just came across it and think there's a simpler answer to any of the ones given here:
<div ng-repeat="data in datas">
<p ng-click="active=!active"
ng-class="{'active': active}">{{data.name}}</p>
</div>
No code needed in the controller other than to create your datas array. This works because the active variable is created within the child scope of each div created by the ng-repeat rather than in the controller's scope.
I've created an updated version of #joshwhatk's codepen here

How to conditionally insert HTML after AngularJs Directive paired with ngRepeat?

=================
First of I just want to say that I'm new to this AngularJs business (started earlier today) and I probably over complicate things.
With that being said let's get down to business, shall we?
I have the following jsFiddle and the html output is as follows.
<div ng-app="test" ng-controller="About" class="hex-grid clearfix ng-scope">
<div class="hex odd" ng-repeat="person in ledning">
<a href="">
<div class="inner">
<h3 class="ng-binding">Just a name</h3>
<hr class="grid-1 center">
<p class="ng-binding">a title</p>
</div>
<div class="hex-1">
<span class="after"></span>
</div>
<div class="hex-2">
<span class="after"></span>
</div>
<span class="after"></span>
</a>
</div>
<!-- and 5 times more -->
</div>
Now, What I want to achieve is this -->http://jsfiddle.net/engstrumpan/yCv79/4/embedded/result/.
This is just plain html (no AngularJs) just to demonstrate what I want to achieve. This particular layout was achieved by inserting <br /> after hexagon 3 and 5
Imagine now if I want a layout like so
1 1 1 1
1 1 1 1 or even 1 1
1 1 1 1 1 1
1 1 1 1
1 1 1
1 1
1
How would one go about implementing this?
This directive is used multiple times so I want it to be as generic as possible.
What I've tried so far is is the following
var app = angular.module('test', []);
app.controller('About', function ($scope) {
$scope.ledning = [
{
...
shouldBreak: true
}
});
app.directive('hexagon', function () {
var tmpl = '<div>
<!-- directive template snippet -->
</div>
<br ng-show="{{data.shouldBreak}}" />';
// rest of code
});
This <br ng-show="{{data.shouldBreak}}" /> won't work since AngularJs throws an exception with the following message Template must have exactly one root element.
Also messing with compile on the directive like so but that results only in the <br /> being inserted after the very last iteration (after the repeater is done working)
var app = angular.module('test', []);
app.directive('hexagon', function () {
return {
...
compile: function($scope, el, attrs){
return function ($scope, el, attrs){
if($scope.data.shouldBreak !== undefined){
el.after('<br />');
}
};
}
}
});
I've read the documentation when it comes to directives and either I'm just stupid or I'm missing the obvious so please help me out.
End of walloftext and maybe some sleep will do the trick. Jump back on the horse tomorrow so to speak.
So I managed to come to a solution after being away for a couple of days and then when I returned to town I tackled my problem again.
The solution
The view
<hexagon ng-repeat="person in ledning" data="person" layout="layout"></hexagon>
The controller:
var app = angular.module('test', []);
app.controller('About', function ($scope) {
...
$scope.layout = [2,4]; // what index (inside ngRepeat) should we break on
}
});
and the directive
app.directive('hexagon', function ($timeout) {
...
return {
...
scope: {
data: '=',
layout: '=' // grab the data bound to 'layout'
},
link: function ($scope, el, attrs) {
$timeout(function(){
for (var i = 0; i < $scope.layout.length; i++) {
if ($scope.layout[i] == $scope.$parent.$index)
el.after('<br />');
}
}, 0);
}
};
});
A working example can be seen here http://jsfiddle.net/engstrumpan/yCv79/5/.
So if I for instance were to work with 10 items I could set $scope.layout = [2,6] to get the following output in this jsFiddle
So how did I end up with this solution? Well I have to thank this guy (Lorenz Merdian) and his blog for it. At least when it comes down tho the use of $timeout to give the browser enough time to render the DOM for the element so I could insert my <br />
It might not be the most elegant solution but it works for me in this particular scenario
You could use pseudoclasses to insert a carriage return "\a" either before :before or after :after the element in question (although if you are supporting legacy browsers, this ain't for you).
CSS:
.last:before {
content: "\A";
white-space:pre;
}
And a conditional ng-class to set the line-breaking class, where item.lineBreak contained true or false depending on its order in the group:
ng-class="{last:item.lineBreak}"
That is the basic answer, and here's the working plunk with the ng-class on the element.
Fork of the plunk with the ng-class on the root element in the template. Same result.
..
..
The tricky/fun part is being able to dynamically insert that class/pseudoclass into the correct elements without hardcoding them. It looked like you wanted a pyramid (Fibonacci wannabe) design (which is what looked interesting about this question).
What I did was use a pretty basic looping process to insert the true property - (from the above example item.lineBreak) - into the items which were the 1st, 3rd, 6th, 10th, (and on) items in the ng-repeat. I put this in a $watch of a select-box model so you could choose the number of items in the group.
Here's the $watch and
var findFib = function(index){
if(index==increment){
rowLength += 1;
increment = rowLength + increment;
return true;
}
return false;
}
$scope.$watch('collectionAmount.value', function(val){
$scope.collection = [];
rowLength = 1;
increment = 1;
var i = 1;
//loop through the items, using the Fibonacci-like
//method to determine which items will recieve the
//lineBreak property of true;
while(i<=val){
var f = findFib(i);
$scope.collection.push({number: i, lineBreak: f});
console.log($scope.collection[i-1]);
i++;
}
//reverse the array if using :before pseudoclass to get upside-down pyramid:
$scope.collection.reverse();
})
From there, the ng-class conditionally applied the .last class which contained the :before pseudoclass that inserted the return. This could easily be scaled to fit your more complex markup.

Resources