AngularJS - 完成ng-repeat后操作DOM

30s*_*sam 9 javascript angularjs ng-repeat

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

我们有一个jQuery滑块插件,它与数据绑定并且正常工作,但是在使用时ng-repeat,我们必须将其初始化包装$timeout起来才能工作 - 现在甚至无法正常工作.

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

调用滑块指令但无法获取滑块中图像的高度,因为图像尚未加载到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();
};
Run Code Online (Sandbox Code Playgroud)

......这是一个复制演示.

Eli*_*lka 38


快速肮脏(演示)

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

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

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

<img class="thumb-recipe" ng-src="{{ object.tile_url }}" load-dispatcher/>
Run Code Online (Sandbox Code Playgroud)

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

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


解耦(演示)

这有点令人不安,因为它不遵守得墨忒耳法则 - 任何观察$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);
        });
    })
    
    Run Code Online (Sandbox Code Playgroud)
    <div ng-repeat="object in videos.objects" ng-controller="LoopWatchCtrl">
    
    Run Code Online (Sandbox Code Playgroud)
  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
        }
    });
    
    Run Code Online (Sandbox Code Playgroud)

在那里,现在我们的指令不必知道模型.但是,它有点麻烦,现在我们必须绑定一个指令一个控制器.


折叠(演示)

让我们将整个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;
                }
            });
        }
    };
});
Run Code Online (Sandbox Code Playgroud)

...将应用于ng-repeated元素:

<div ng-repeat="object in videos.objects" class="slide" image-load-watcher>
Run Code Online (Sandbox Code Playgroud)

现在我们可以简单地$allImagesLoaded在滑块中观看:

scope.$on('$allImagesLoaded', function() {
    _initSlider(element);
});
Run Code Online (Sandbox Code Playgroud)


概括它(如果你想)(演示)

我们可以再次拆分并在应用程序范围内应用此方法来使用任何 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;
        });
        // ...
    });
    
    Run Code Online (Sandbox Code Playgroud)
  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;
        });
    });
    
    Run Code Online (Sandbox Code Playgroud)
  3. 现在可以在任何地方捕获事件,例如在slider指令中:

    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
        }
    });
    
    Run Code Online (Sandbox Code Playgroud)

它似乎打败了目的,因为我们已经回到原点,但它可能非常强大.而且 - 看,妈妈,没有新的指令!

<div ng-repeat="object in videos.objects" class="slide">
    <img class="thumb-recipe" ng-src="{{ object.tile_url }}"/>
</div>
Run Code Online (Sandbox Code Playgroud)


穿上袜子(演示)


     TL;DR, just gimme tha demo ! ! !     
 



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


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

  • 你先生,值得为javascript编程获得奖牌!`newVal && scope.$ emit('$ repeatFinished');`!! 心灵狂欢. (2认同)