I have a filter that changes filtered object. But when I'm using ng-style="item.gridSize"
My Filter: (The Algorithm for size Grid was taken (changed for my needs) from Here
angular.module("custom.modules.photoGrid", []).filter('photoSearch', [function () {
function getGrid(photos){
var output = [];
var HEIGHTS = [];
var COLUMN_WIDTH = 227;
var MARGIN = 6;
var DELTA = 20;
var size = window.innerWidth - 50;
var n_columns = Math.floor(size / (2 * (COLUMN_WIDTH + MARGIN)));
create_columns(n_columns);
var small_images = [];
function create_columns(n) {
HEIGHTS = [];
for (var i = 0; i < n; ++i) {
HEIGHTS.push(0);
}
}
function get_min_column() {
var min_height = Infinity;
var min_i = -1;
for (var i = 0; i < HEIGHTS.length; ++i) {
if (HEIGHTS[i] < min_height) {
min_height = HEIGHTS[i];
min_i = i;
}
}
return min_i;
}
function gridSize(i, is_big) {
var size = {
'margin-left': (MARGIN + (COLUMN_WIDTH + MARGIN) * i)+'px',
'margin-top': (HEIGHTS[Math.floor(i / 2)] * (COLUMN_WIDTH + MARGIN))+'px',
'width': is_big ? (COLUMN_WIDTH * 2 + MARGIN)+'px' : COLUMN_WIDTH+'px',
'height': is_big ? (COLUMN_WIDTH * 2 + MARGIN)+'px' : COLUMN_WIDTH+'px'
};
return size;
}
function createGrid(data){
if (data.length >= 2) {
for(var i = 0; i < data.length; i++){
var column = get_min_column();
if (Math.random() > 0.8) {
data[i]['gridSize'] = gridSize(column * 2, true);
HEIGHTS[column] += 2;
} else {
small_images.push(i);
if (small_images.length === 2) {
data[small_images[0]]['gridSize'] = gridSize(column * 2, false);
data[small_images[1]]['gridSize'] = gridSize(column * 2 + 1, false);
HEIGHTS[column] += 1;
small_images = [];
}
}
}
if (small_images.length) {
column = get_min_column();
data[(data.length-1)]['gridSize'] = gridSize(column * 2, false);
}
}
return data;
}
var grid = createGrid(photos);
return grid;
}
return function(photos, search) {
var filtered = [];
if(!!search){ /**@case1 if only search query is present**/
search = search.toLowerCase();
for(var i = 0; i < photos.length; i++){
if(photos[i].photo_name.toLowerCase().indexOf(search) !== -1){
filtered.push(photos[i]);
}
}
}else {
/**@case2 no query is present**/
filtered = photos;
}
filtered = getGrid(filtered);
return filtered;
}
}]);
Html:
<input type="text" ng-model="input.value"> <span>{{ results.length }}</span> Photo Found
<div ng-repeat='photo in photos | photoSearch:input.value as results track by photo.id' class="photo-item" ng-style="photo.gridSize">
<img ng-src="/photos/{{photo.url}}">
</div>
A small explanation:
Every time ng-model
input.value
changed filter is runed and creates different grid for filtered array of photos. all dimensions are written inside gridSize
and this cause digest loop.
What I've tried until now: I've moved my ng-repeat
in directive, but this way I can't access result.length
and input.value
.
I've also tried a bindonce
directive but using it like bo-style="photo.gridSize"
doesn't change the grid after user search(and is logically right because is bidden only once, but values changed.
So my question is how to make ng-repeat
assign new grdiSize
property without running in digest loop.
UPDATE: JSFiddle
Working Fiddle: JSFiddle
There were a couple of issues. It was not exactly a ng-style
problem, but rather than in each digest cycle your photos were calculating different style objects, causing another digest cycle to run.
Some issues I've found:
NaN
when calculating margin-top
and failing. To fix this, I added a default value from 1 column.Math.random() > 0.8
was giving different results in each time your filter function was executing. In each digest cycle, since Math.random()
gives different results, it was forcing another digest loop (you were updating gridSize object - since there's a $watch
for each element in the ng-repeat
it detects the changes and forces one digest cycle), and so on. That was the error log in console.I created this fiddle that works. The main changes are
defined a fixed random value for each photo, after declaring your array
$scope.photos.forEach(function(onePhoto){
onePhoto.randomValue = Math.random();
});
then in the filter you would check against this value
if (data[i].randomValue > 0.8) {
}
and set a minimum of 1 column when creating columns
var n_columns = Math.max(1, Math.floor(size / (2 * (COLUMN_WIDTH + MARGIN))));
Also (but I believe this occured only in your fiddle), there was no photo_name
to filter for, so I used id
instead.
you might want to fix the NaN
problem with other default value, but at least now you know the problems with the console error.
If you wanted to update your randomValue everytime you executed the search, you could place a $watch
over your input.value
, move the code that iterates photos and creates random values into a function, and use that in the callback for that watch function. So everytime you update results in search, your grid uses different random values without causing interfering with digest cycle.
Something like this
var updateRandomValues = function() {
$scope.photos.forEach(function(onePhoto){
onePhoto.randomValue = Math.random();
});
};
updateRandomValues();
$scope.$watch('input.value', function(newVal, oldVal) {
if (newVal !== oldVal) {
updateRandomValues();
}
});
However, if you want to get different css style only when you get different results (keep in mind that if you type and get same results as before, it will update your grid layout anyway), what you should $watch
is the results variable instead. Like this
$scope.$watch('results', function(newVal, oldVal) {
if (newVal !== oldVal) {
updateRandomValues();
}
});