一尘不染

AngularJS-在ng-repeat完成后操作DOM

angularjs

在遍历数据后,我遇到一些有关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();
};

……这是一个 复制演示


阅读 276

收藏
2020-07-04

共1个答案

一尘不染

快速删除内容(演示)

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

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!
    }
});

解耦(演示)

这有点令人不安,因为它不遵守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">
  1. 现在,捕获事件并相应地激活滑块初始化:
        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
        }
    });

在那里,现在我们的指令不必了解模型。但是,这有点麻烦,现在我们必须将指令 控制器绑定在一起。

折叠(演示)

让我们将整个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-repeated元素:

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

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

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

将其概括(如果需要)(演示)

我们可以再次对其进行分解,并在整个应用程序中应用此方法,以对 任何 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;
        });
        // ...
    });
  1. 装饰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;
        });
    });
  1. 现在可以在任何地方捕获事件,例如在您的滑块指令中:
        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
        }
    });

当我们回到第一个问题时,这似乎无法达到目的,但它可能非常强大。还有-看,妈妈,没有新指令!

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

(演示)

TL;DR, just gimme tha demo!!!


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


link1.3.x版开始,将无法覆盖该功能。

2020-07-04