Angular Digest Loop on ng-style / Dynamic Photo Grid

时间:2017-03-05 09:40:37

标签: javascript css angularjs angularjs-directive angularjs-scope

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>

一个小解释: 每次ng-model input.value更改过滤器都会被修改,并为过滤后的照片数组创建不同的网格。所有维度都写在gridSize内,这会导致摘要循环。

到目前为止我尝试过的内容:我已将ng-repeat移至指令中,但这样我就无法访问result.lengthinput.value

我还尝试了bindonce指令,但是像bo-style="photo.gridSize"一样使用它并不会在用户搜索后更改网格(并且在逻辑上是正确的,因为只出价一次,但值已更改。

所以我的问题是如何在不在摘要循环中运行的情况下ng-repeat分配新的grdiSize属性。

更新: JSFiddle

工作小提琴: JSFiddle

1 个答案:

答案 0 :(得分:1)

有几个问题。这不是一个ng-style问题,而是在每个摘要周期中,您的照片计算不同的样式对象,导致另一个摘要周期运行。

我发现了一些问题:

  • 逻辑错误是给出了0列,因此在计算NaN和失败时导致大小给出margin-top。为了解决这个问题,我在1列中添加了一个默认值。
  • 您的Math.random() > 0.8在每次执行过滤器功能时都会给出不同的结果。在每个摘要周期中,由于Math.random()给出了不同的结果,它正在强制另一个摘要循环(您正在更新gridSize对象 - 因为$watch中的每个元素都有ng-repeat它会检测到更改并强制一个消化周期),等等。那是控制台中的错误日志。

我创建了这个有效的fiddle。主要变化是

在声明数组后为每张照片定义了固定的随机值

$scope.photos.forEach(function(onePhoto){
    onePhoto.randomValue = Math.random();
  });

然后在过滤器中,您将检查此值

if (data[i].randomValue > 0.8) {
}

并在创建列时设置至少1列

var n_columns = Math.max(1, Math.floor(size / (2 * (COLUMN_WIDTH + MARGIN))));

另外(但我相信这只发生在你的小提琴中),没有photo_name来过滤,所以我使用id代替。

您可能希望使用其他默认值修复NaN问题,但至少现在您知道控制台错误的问题。

如果您想在每次执行搜索时更新randomValue,可以在$watch上放置input.value,移动迭代照片的代码并在函数中创建随机值,然后使用在该监视功能的回调中。因此,每当您在搜索中更新结果时,您的网格都会使用不同的随机值,而不会干扰摘要周期。 像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();
    }
  });

但是,如果你想获得不同的css样式只有当你得到不同的结果时(请记住,如果你输入和得到的结果一样,它会更新你的网格布局),你应该{{1}而是结果变量。像this

一样
$watch