AngularJS - 完成ng-repeat后操作DOM

时间:2014-03-13 15:56:39

标签: javascript angularjs ng-repeat

在循环数据后,我遇到了一些关于操纵DOM的问题。

我们有一个jQuery滑块插件,它与数据绑定并且正常工作,但是当使用ng-repeat时,我们必须用$timeout包装它的初始化才能使它工作 - 现在这不是甚至工作。

我认为使用$timeout是不可靠的,这会导致错误的修复。在jQuery中,我可以使用$(document).ready() - 这是可靠的,但使用angular.element(document).ready()似乎也不起作用。

调用slider指令但无法获取滑块中图像的高度,因为图像尚未加载到DOM中 - 导致滑块的计算高度为0。

我现在发现它非常令人沮丧 - 必须有一种方法可以在数据(例如ng-repeat)循环之后操纵DOM。

滑块的初始化按如下方式执行:

var sliderLoad = function () {
    $timeout(function () {
        var setHeight = elem.find('.slide:eq(0)').outerHeight(true);
        elem.css({
            height: setHeight
        });
    }, 1000);
    // Show the slider nav buttons
    elem.parent().find('.direction-nav').show();
};

...这是一个 reproduction demo

1 个答案:

答案 0 :(得分:37)


快速 - ' n-Dirty it (Demo)

我们希望确保所有图片都已加载,所以让我们为此写一个指令:

app.directive('loadDispatcher', function() {
    return {
        restrict: 'A',
        link: function(scope, element, attrs) {
            element.bind('load', function() {
                scope.$emit('$imageLoaded');
            });
        }
    };
})

...并将其附加到ng-src' d元素:

<img class="thumb-recipe" ng-src="{{ object.tile_url }}" load-dispatcher/>

现在我们可以比较我们模型捕获的事件数量,并根据需要采取行动:

var loadCount = 0;
scope.$on('$imageLoaded', function () {
    if (loadCount++ === scope.videos.objects.length - 1) {
        _initSlider(); // act!
    }
});


将其解耦 (Demo)

这有点令人不安,因为它没有遵守Law of Demeter - 任何会指示$imageLoaded事件必须知道该模型的指令(scope.videos.objects.length )。

我们可以通过找出加载了多少图像而不明确地解决模型来防止这种耦合。我们假设事件将在ng-repeat处理。

  1. 确保ng-repeat已完成,并触发包含项目计数的事件。我们可以通过附加一个唯一目的的控制器来做到这一点 - 观察$last属性。一旦找到(具有真实价值),我们将触发一个事件来通知它:

    .controller('LoopWatchCtrl', function($scope) {
        $scope.$watch('$last', function(newVal, oldVal) {
            newVal && $scope.$emit('$repeatFinished', $scope.$index);
        });
    })
    
    <div ng-repeat="object in videos.objects" ng-controller="LoopWatchCtrl">
    
  2. 现在,捕捉事件并相应地激活滑块初始化:

    var loadCount = 0,
        lastIndex = 0;
    
    scope.$on('$repeatFinished', function(event, data) {
        lastIndex = data;
    });
    
    scope.$on('$imageLoaded', function() {
        if (lastIndex && loadCount++ === lastIndex) {
            _initSlider(element); // this is defined where-ever
        }
    });
    
  3. 在那里,现在我们的指令不必知道模型。但是,它有点麻烦,现在我们必须绑定一个指令一个控制器。


    折叠 (Demo)

    让我们将整个shabang提取到一个指令中:

    app.directive('imageLoadWatcher', function($rootScope) {
        return {
            restrict: 'A',
            link: function(scope, element, attrs) {
                if (typeof $rootScope.loadCounter === 'undefined') {
                    $rootScope.loadCounter = 0;
                }
                element.find('img').bind('load', function() {
                    scope.$emit('$imageLoaded', $rootScope.loadCounter++);
                });
            },
            controller: function($scope) {
                $scope.$parent.$on('$imageLoaded', function(event, data) {
                    if ($scope.$last && $scope.$index === $rootScope.loadCounter - 1) {
                        $scope.$emit('$allImagesLoaded');
                        delete $rootScope.loadCounter;
                    }
                });
            }
        };
    });
    

    ...将应用于ng-repeat ed元素:

    <div ng-repeat="object in videos.objects" class="slide" image-load-watcher>
    

    现在我们只需观看$allImagesLoaded,例如在滑块中:

    scope.$on('$allImagesLoaded', function() {
        _initSlider(element);
    });
    


    概括它(如果你想) (Demo)

    我们可以再次对其进行细分并在应用范围内应用此方法,以便为任何 ng-repeat完成或ng-src加载使用事件调度,这并不总是必要的 1 ,但可能非常有用。我们来看看如何:

    1. 装饰ng-src指令,以便在加载图像时发送事件:

      app.config(function($provide) {
          $provide.decorator('ngSrcDirective', function($delegate) {
              var directive = $delegate[0],
                  link = directive.link;
              directive.compile = function() {
                  return function(scope, element, attrs) {
                      link.apply(this, arguments);
                      element.bind('load', function() {
                          scope.$emit('$imageLoaded');
                      });
                  };
              };
      
              return $delegate;
          });
          // ...
      });
      
    2. 装饰ng-repeat以在完成时通知:

      app.config(function($provide) {
          // ...
          $provide.decorator('ngRepeatDirective', function($delegate) {
              var directive = $delegate[0],
                  link = directive.link;
              directive.compile = function() {
                  return function(scope, element, attrs) {
                      link.apply(this, arguments);
                      scope.$watch('$$childTail.$last', function(newVal, oldVal) {
                          newVal && scope.$emit('$repeatFinished');
                      });
                  };
              };
      
              return $delegate;
          });
      });
      
    3. 现在可以在任何地方捕捉事件,例如在您的滑块指令中:

      var repeatFinished = false;
      var loadCount = 0;
      
      scope.$on('$repeatFinished', function() {
          repeatFinished = true;
      });
      
      scope.$on('$imageLoaded', function () {
          if (repeatFinished && 
                      loadCount++ === scope.videos.objects.length - 1) {
              _initSlider(); // this is defined where-ever
          }
      });
      
    4. 它似乎打败了目的,因为我们重新回到原点,但它可能非常强大。而且 - 看,妈妈,没有新指令!

      <div ng-repeat="object in videos.objects" class="slide">
          <img class="thumb-recipe" ng-src="{{ object.tile_url }}"/>
      </div>
      


      放入袜子 (Demo)


           TL;DR, just gimme tha demo ! ! !     
       



      1 即可。必须仔细考虑装饰,因为结果将在整个应用程序中加载的每个图像上发送一个事件。 功能


      从版本1.3.x开始,将无法覆盖link功能。 功能