nest ng-repeat generates much more items than expected - angularjs

Main idea:
I want to combine bootstrap grid system with angular ng-repeat
Method:
I used filter to reformat the json data (an array with a lot of objects), like the codepen project:
http://codepen.io/maggiben/pen/sfCnq
```
filter('listToMatrix', function() {
return function listToMatrix(list, elementsPerSubArray) {
var matrix = [], i, k;
console.log("hellowrld")
for (i = 0, k = -1; i < list.length; i++) {
if (i % elementsPerSubArray === 0) {
k++;
matrix[k] = [];
}
matrix[k].push(list[i]);
}
return matrix;
};
});
And here is my jade page code:
My controller code: To get the news.
The problem is I got much more items on the page:

This error means angular doesn't manage to finish a digest cycle. The reason for it comes from your filter: every time angular applies the filter to your list, a new matrix is created, so angular will keep invoking the filter until what it returns is the same as the previous iteration (which never happens).
To fix it, you could either track the items of your matrix using ng-repeat ... track by items.someProperty, so after two consecutives calls to your filter, angular will detect that this someProperty has not changed and will finish the cycle.
Another way to fix it would be to cache the result of your filter for a given list, so the next time angular invoyes your filter with the same list, you would return a pointer to the same matrix.
For more lecture you can refer to this question (same problem): Angular filter works but causes "10 $digest iterations reached"

Related

ui-sortable acting strange with ng-model

In my AngularJS application I created a ui-sortable with a list. When I try to order the list different, it is acting stange. Without a ng-model I do not have this problem, but ui-sortable requires to have a ng-model, otherwise it will print an error in the console. Why does the ng-model create this problem? Do I have created my model object in a wrong?
I reproduces the error in this jsFiddle.
To reproduce: drag Laurent to the bottom of the list, and Laurent will appear in the middle, and not on the bottom where it should be.
I hope someone can help me with this problem.
It appears to be a bug in ui-sortable, you're using an extremely oudated version of the both libraries.
check out the source code at
https://github.com/angular-ui/ui-sortable/blob/v0.0.1/src/sortable.js
and compare it with your version
https://cdnjs.cloudflare.com/ajax/libs/angular-ui/0.4.0/angular-ui.js
after some debuging you might notice there is
onStop = function(e, ui) {
// digest all prepared changes
if (ui.item.sortable.resort && !ui.item.sortable.relocate) {
// Fetch saved and current position of dropped element
var end, start;
start = ui.item.sortable.index;
end = ui.item.index();
if (start < end)
end--;
// Reorder array and apply change to scope
ui.item.sortable.resort.$modelValue.splice(end, 0, ui.item.sortable.resort.$modelValue.splice(start, 1)[0]);
}
if (ui.item.sortable.resort || ui.item.sortable.relocate) {
scope.$apply();
}
};
remove that if statement start < end then end-- and it will work. onStop should look like this after
onStop = function(e, ui) {
// digest all prepared changes
if (ui.item.sortable.resort && !ui.item.sortable.relocate) {
// Fetch saved and current position of dropped element
var end, start;
start = ui.item.sortable.index;
end = ui.item.index();
// Reorder array and apply change to scope
ui.item.sortable.resort.$modelValue.splice(end, 0, ui.item.sortable.resort.$modelValue.splice(start, 1)[0]);
}
if (ui.item.sortable.resort || ui.item.sortable.relocate) {
scope.$apply();
}
};
I do not recommend manual changes to library code, rather upgrade to new version of the code, there might be more features and more bug fixes.

AngularFire how to remove multiple items from array?

I am using Firebase and the AngularFire library. I am looking for a way to remove all items or a range of items from a $firebaseArray object. I don't see a straightforward way to do it in the documentation. Is there some way I'm not thinking of other than looping and removing items one by one? Please tell me that's not the only way!!
If there isn't a method in the $firebaseArray that does what you want, you can use the array's $ref() to perform Firebase SDK-style calls. The array content will be synchronized with the changes you make through the ref.
To delete all elements, call remove on the ref itself:
function removeAll(firebaseArray) {
return firebaseArray.$ref().remove();
}
To remove a range, perform an update in which the keys to be removed are set to null:
function removeRange(firebaseArray, start, end) {
var keys = {};
if (end === undefined) {
end = firebaseArray.length;
}
for (var i = start; i < end; ++i) {
keys[firebaseArray.$keyAt(i)] = null;
}
return firebaseArray.$ref().update(keys);
}
Both functions return promises.
I couldn't get the firebaseArray.$ref().remove() to work as the remove() function didn't exist on the object when ordered by child, but doing the following seemed to work though:
$firebaseUtils.doRemove(firebaseArray.$ref());

Angular: infinite digest loop in filter

I'm writing a custom Angular filter that randomly capitalizes the input passed to it.
Here's the code:
angular.module('textFilters', []).filter('goBananas', function() {
return function(input) {
var str = input;
var strlen = str.length;
while(strlen--) if(Math.round(Math.random())) {
str = str.substr(0,strlen) + str.charAt(strlen).toUpperCase() + str.substr(strlen+1);
}
return str;
};
});
I call it in my view like so:
<a class='menu_button_news menu_button' ng-href='#/news'>
{{"News" | goBananas}}
</a>
It works, but in my console I'm seeing a rootScope:infdig (infinite digest) loop.
I'm having some trouble understanding why this is happening and what I can do to resolve it. If I understand correctly, this is due to the fact that there are more than 5 digest actions called by this function. But the input is only called once by the filter, right?
Any help appreciated.
The problem is that the filter will produce a new result every time it is called, and Angular will call it more than once to ensure that the value is done changing, which it never is. For example, if you use the uppercase filter on the word 'stuff' then the result is 'STUFF'. When Angular calls the filter again, the result is 'STUFF' again, so the digest cycle can end. Contrast that with a filter that returns Math.random(), for example.
The technical solution is to apply the transformation in the controller rather than in the view. However, I do prefer to transform data in the view with filters, even if the filter applies an unstable transformation (returns differently each time) like yours.
In most cases, an unstable filter can be fixed by memoizing the filter function. Underscore and lodash have a memoize function included. You would just wrap that around the filter function like this:
.filter('myFilter', function() {
return _memoize(function(input) {
// your filter logic
return result;
});
});
Since digest will continue to run until consistent state of the model will be reached or 10 iterations will run, you need your own algorithm to generate pseudo-random numbers that will return the same numbers for the same strings in order to avoid infinite digest loop. It will be good if algorithm will use character value, character position and some configurable seed to generate numbers. Avoid using date/time parameters in such algorithm. Here is one of possible solutions:
HTML
<h1>{{ 'Hello Plunker!' | goBananas:17 }}</h1>
JavaScript
angular.module('textFilters', []).
filter('goBananas', function() {
return function(input, seed) {
seed = seed || 1;
(input = input.split('')).forEach(function(c, i, arr) {
arr[i] = c[(c.charCodeAt(0) + i + Math.round(seed / 3)) % 2 ? 'toUpperCase' : 'toLowerCase']();
});
return input.join('');
}
});
You can play with seed parameter to change a bit an algorithm. For example it may be $index of ngRepeat
Plunker: http://plnkr.co/edit/oBSGQjVZjhaIMWNrPXRh?p=preview
An alternative, if you want the behaviour to be truly random, is to do deal with the randomness only once during linking by creating a seed, and then use a seeded random number generator in the actual filter:
angular.module('textFilters', []).filter('goBananas', function() {
var seed = Math.random()
var rnd = function () {
var x = Math.sin(seed++) * 10000;
return x - Math.floor(x);
}
return function(input) {
var str = input;
var strlen = str.length;
while(strlen--) if(Math.round(rnd())) {
str = str.substr(0,strlen) + str.charAt(strlen).toUpperCase() + str.substr(strlen+1);
}
return str;
};
});

$rootScope:infdig error caused by filter?

I'm doing a filter on an array in an ng-repeat.
div.row(ng-repeat="log in logs | prepare_log | orderBy: 'log.created'")
In the prepare_log filter I'm doing this:
value.stack_trace = angular.fromJson(value.stack_trace)
If I change it to this:
value.stack_trace = angular.fromJson(value.stack_trace).reverse()
I get the infdig error.
I don't know if it's relevant, but I'm iterating over the stack_trace property in an inner ng-repeat.
Any ideas what I'm doing wrong here?
You are causing an infinite $digest loop because you are changing the model during the digest loop.
What happens behind the scenes is this:
ng-repeat parses the collection expression to figure out what rows need to be 'stamped' out and instantiates a watcher to get notified when the collection changes
every time the filter is run, you change one of the items by assigning a new value to value.stack_trace, triggering the watcher of the ng-repeat to pick this up and starting over and over again
Angular detects the loop and aborts
To solve the problem avoid changing the model in your filter.
Hope that helps!
because angular will always trigger digest one more time to make sure there is no more changes. in every digest cycle prepare_log filter will be called and return a value. if return value is the same from last one, it means no more changes and the application state is stabilized, thus angular does not have to trigger an extra digest.
but in your filter, value.stack_trace will be reversed in every digest cycle, thus the application state is never stabilized, which causing infdig (infinite digest) error.
Solving infdig errors caused by filters in an ngRepeat can be cumbersome and annoying. When you just want a quick fix, it is often enough to articulate as long as the input array doesn't change in order or size, give me the same result.
This is made a lot easier if the models you're dealing with all have a unique id property.
In that case, we like to deploy a generic filter stabilization approach:
angular
.module( "app" )
.factory( "stabilize", stabilizeFactory );
/**
* Generalization of a filter stabilization approach.
* As long as the input contains the same elements (identified by their `id`) and they are in the same order,
* the same result array is returned, thus preventing infdig errors.
*/
function stabilizeFactory() {
return function stabilize( filterFunc ) {
return function stableFilter() {
var array = arguments[ 0 ];
if( !array || !array.length ) {
return array;
}
var stabilizationId = idString( array );
if( array.$$stable ) {
if( array.$$stableId === stabilizationId ) {
return array.$$stable;
}
}
array.$$stable = filterFunc.apply( arguments );
array.$$stableId = stabilizationId;
return array.$$stable;
};
};
function idString( array ) {
return array.reduce( function appendKey( id, element ) {
return id + element.id;
}, "" );
}
}
To use this, just wrap your own filter function in stabilize(), like so:
angular
.module( "app" )
.filter( "myFilter", myFilterProvider);
/** #ngInject */
function myFilterProvider( stabilize ) {
return stabilize( myFilter);
function myFilter( array ) {
if( !array || !array.length ) {
return array;
}
return array.filter( function( element ) {
return element.something === "foo";
}
);
}
}

Angular JS with Google Feed API - strange behaviour

In one of my controller functions in my angular.js application, I'm trying to use Google's RSS Feed API to fetch feed entries to populate in my scope.items model.
It's behaving quite strangely, with the console output in the innermost part always being '2' but while the console output in the outermost loop being '0' and '1' (which is correct since that is the length of my items array).
I'm thinking it could have something to do with the Google API thing being an async request and that it hasn't finished before I try to manipulate it (?). Still doesn't make sense that my iterator variable would become '2' though!
Code:
function ItemListCtrl($scope, Item) {
$scope.items = Item.query({}, function() { // using a JSON service
for (var i = 0; i < $scope.items.length; i++) { // the length is 2 here.
$scope.items[i].entries = [];
console.log(i); // gives '0' and '1' as output in the iterations.
var feed = new google.feeds.Feed($scope.items[i].feedUrl);
feed.load(function(result) {
console.log(i); // gives '2' as output in both the iterations.
if (!result.error) {
for (var j = 0; j < result.feed.entries.length; j++) {
$scope.items[i].entries[j] = result.feed.entries[j];
}
}
});
}
});
}
The callback function is executed asynchonously, after the loop over the items has ended. And at the end of the loop, i is equal to 2, since there are 2 items in your items array.
See Javascript infamous Loop issue? for another example of the same behavior, and https://developer.mozilla.org/en-US/docs/Web/JavaScript/Guide/Closures#Creating_closures_in_loops.3A_A_common_mistake for an more in-depth explanation.

Resources