寻找一种更好的方法来循环并在AngularJS中一次显示一个元素

时间:2013-07-06 14:40:56

标签: angularjs

我正在构建一个包含动画的应用程序,我需要它才能更好地工作。我希望能够以指定的动画间隔浏览一系列div并一次显示一个div。我希望每个系列都有自己的速度和自己的div。

这是我到目前为止的内容(也在下面复制):http://jsfiddle.net/ollerac/shkq7/

基本上,我正在寻找一种方法将setInterval放在animatedBox的属性上,这样我就可以创建一个带有自定义属性的新animatedBox。但每次我尝试这样做都会打破。

HTML

<div ng-app ng-controller="BoxController">

    <div class="layer" ng-repeat="layer in animatedBox.layers" ng-style="{ 'backgroundColor': layer.color}" ng-show="layer == animatedBox.selectedLayer"></div>

</div>


JAVASCRIPT

function buildBox () {
    return {
        color: '#' + (Math.random() * 0xFFFFFF << 0).toString(16)
    }
}

function BoxController ($scope) {
    $scope.animatedBox = {
        layers: [],
        selectedLayer: null
    };

    for (var i = 0; i < 5; i++) {
        $scope.animatedBox.layers.push(buildBox());
    }

    var i = -1;
    setInterval(function () {
        $scope.$apply(function() {
            i++;

            if (i < $scope.animatedBox.layers.length) {
                $scope.animatedBox.displayThisLayer = $scope.animatedBox.layers[i];
            } else {
              i = 0;
              $scope.animatedBox.selectedLayer = $scope.animatedBox.layers[i];
            }
        });
    }, 500);
}


CSS

.layer {
    width: 30px;
    height: 30px;
    position: absolute;
}

*更新*

以下是我想做的事情:

更新了jsFiddle:http://jsfiddle.net/ollerac/shkq7/2/

function buildBox () {
    return {
        color: '#' + (Math.random() * 0xFFFFFF << 0).toString(16)
    }
}

function BoxController ($scope) {
    $scope.animatedBox = {
        layers: [],
        selectedLayer: null,
        selectedLayerIndex: -1,
        updateSelectedLayer: function () {
            var self = this;

            if (self.layers.length) {
                $scope.$apply(function() {
                    self.selectedLayerIndex++;

                    if (self.selectedLayerIndex < self.layers.length) {
                        self.selectedLayer = self.layers[self.selectedLayerIndex];
                    } else {
                        self.selectedLayerIndex = 0;
                        self.selectedLayer = self.layers[self.selectedLayerIndex];
                    }
                });
            }
        }
    };

    for (var i = 0; i < 5; i++) {
        $scope.animatedBox.layers.push(buildBox());
    }

    setInterval(function () {
        $scope.animatedBox.updateSelectedLayer();
    }, 500);    
}

现在,对象更新了自己的selectedLayer属性。 但是我仍然需要调用单独调用更新的setInterval以使其更新。但我希望对象能够自我更新并完全独立。你能想出一个很好的方法来做到这一点,因为我真的很擅长......

我想这更像是一个普通的javascript问题,但我认为可能有一种Angular方式来处理这种情况,比如可能使用指令或某些东西是合适的。

任何建议都非常赞赏。

1 个答案:

答案 0 :(得分:2)

你是对的,我相信指令是正确的解决方案。 (顺便说一下,这个很有趣。)

当接近这样的问题时,我通常首先编写我希望我能写的HTML和控制器,如果一切都已经有效的话。对于这个例子,这是我最终的结果。

<div ng-controller="BoxController">
  <div animated-boxes="colors"></div>
</div>
app.value('randomColor', function() {
  var red   = Math.floor(Math.random() * 255);
  var green = Math.floor(Math.random() * 255);
  var blue  = Math.floor(Math.random() * 255);
  return "rgb(" + red + "," + green + "," + blue + ")";
});

app.controller('BoxController', function($scope, randomColor) {
  $scope.colors = [ randomColor(), randomColor() ];
});

这里,控制器仅 负责设置范围内的一些基本数据 - 一组颜色; DOM 非常简单,只将该数组传递给名为animated-boxes的东西。 randomColor已移至服务中,因此可以更轻松地重复使用和测试。 (我也稍微改了一下,因此不会导致错误的十六进制值。)

现在,唯一尚未发挥作用的部分就是这个名为animated-boxes的东西。每当您想要与DOM交互时,或者在使用HTML属性时触发某些行为,我们都会转向指令。

我们将定义我们的指令,注入$timeout服务,因为我们知道我们想要做基于计时器的东西。该指令的结果只是一个对象。

app.directive('animatedBoxes', function($timeout) {
  return {
  };
});

由于我们希望指令是自包含的并且不会弄乱它所包含的外部范围,我们将给它一个隔离范围(有关更多信息,请参阅directive docs,但基本上这只是意味着除了通过我们指定的变量之外,我们的范围没有附加到指令所在的范围。)

由于我们希望通过HTML属性访问传递给指令的值,因此我们将在该值上设置双向范围绑定;我们称之为colors

app.directive('animatedBoxes', function($timeout) {
  return {
    scope: {
      colors: '=animatedBoxes'
    }
  };
});

我们将为其提供一个简单的模板,该模板循环遍历colors,并为每种颜色输出div s之一。我们的ng-show表示只有在范围值div等于selected时才会显示$indexng-repeat是{{1}的当前迭代的数组索引循环。

app.directive('animatedBoxes', function($timeout) {
  return {
    scope: {
      colors: '=animatedBoxes'
    },
    template: "<div><div class='layer' ng-repeat='color in colors' " +
      "ng-style='{backgroundColor: color}' ng-show='selected == $index'>" +
      "</div></div>"
  };
});

现在为链接函数 - 将处理指令逻辑的函数。首先,我们想要跟踪我们正在展示哪个盒子;在我们的ng-show中,我们使用了selected。我们还想跟踪我们有多少箱子;我们将在我们的指令范围内使用$watch来跟上这一点。

link: function(scope, elem, attrs) {
  scope.selected = 0;
  var count = 0;

  scope.$watch('colors', function(value) {
    // whenever the value of `colors`, which is the array
    // of colors passed into the directive, changes, update
    // our internal count of colors
    if (value) count = value.length;
    else count = 0; // if `colors` is falsy, set count to 0
  }, true); // `true` ensures we watch the values in the array,
            // not just the object reference
}

最后,我们需要经常循环每个盒子。我们将使用$timeout执行此操作,setTimeout$apply的一个版本,其中包含范围var nextBox = function() { if (scope.selected >= count - 1) scope.selected = 0; else scope.selected++; // recursively use `$timeout` instead of `setInterval` $timeout(nextBox, 500); }; // kick off the directive by launching the first `nextBox` nextBox(); 调用(它执行其他一些操作,但我们现在不关心它)。

app.directive('animatedBoxes', function($timeout) {
  return {
    scope: {
      colors: '=animatedBoxes'
    },
    template: "<div><div class='layer' ng-repeat='color in colors' " +
      "ng-style='{backgroundColor: color}' ng-show='selected == $index'>" +
      "</div></div>",
    link: function(scope, elem, attrs) {
      scope.selected = 0;
      var count = 0;

      scope.$watch('colors', function(value) {
        if (value) count = value.length;
        else count = 0;
      }, true);

      var nextBox = function() {
        if (scope.selected >= count - 1) scope.selected = 0;
        else scope.selected++;
        $timeout(nextBox, 500);
      };

      nextBox();
    }
  };
});

如果你把整个指令放在一起,你最终会得到这个代码(删除注释):

colors

一个完整的工作示例,包括注释和一些调试区域,您可以在其中查看colors的值并与之交互(以便您可以看到该指令如何响应控制器中的更改) :http://jsfiddle.net/BinaryMuse/g6A6Y/

现在您已经拥有了这个,请考虑尝试应用这些知识,以允许指令通过DOM传递它具有可变速度,就像我们对{{1}}所做的那样。这是我的结果:http://jsfiddle.net/BinaryMuse/cHHKn/