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

声明:本页面是StackOverFlow热门问题的中英对照翻译,遵循CC BY-SA 4.0协议,如果您需要使用它,必须同样遵循CC BY-SA许可,注明原文地址和作者信息,同时你必须将它归于原作者(不是我):StackOverFlow 原文地址: http://stackoverflow.com/questions/22384078/
Warning: these are provided under cc-by-sa 4.0 license. You are free to use/share it, But you must attribute it to the original authors (not me): StackOverFlow

提示:将鼠标放在中文语句上可以显示对应的英文。显示中英文
时间:2020-10-27 23:02:28  来源:igfitidea点击:

AngularJS - Manipulating the DOM after ng-repeat is finished

javascriptangularjsng-repeat

提问by 30secondstosam

I'm experiencing some problems regarding manipulation of the DOM after looping through the data.

我在遍历数据后遇到了一些关于 DOM 操作的问题。

We have a jQuery slider plugin that is tied to data and works normally, but when using ng-repeat, we have to wrap its initialization with $timeoutfor it to work — and now that isn't even working.

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

I think using $timeoutis unreliable, which makes for a bad fix. In jQuery I could use $(document).ready()— which was solid, but using angular.element(document).ready()doesn't seem to work either.

我认为使用$timeout是不可靠的,这是一个不好的修复。在 jQuery 中我可以使用$(document).ready()——这是可靠的,但使用angular.element(document).ready()似乎也不起作用。

The slider directive is invoked but cannot obtain the height of the images in the slider because the images have not been loaded into the DOM — resulting in the slider having a calculated height of 0.

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

I'm finding it very frustrating at the moment - there must be a way to manipulate the DOM after data (in an ng-repeatfor example) has cycled through.

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

The Initialization of the slider is performed as follows:

滑块的初始化执行如下:

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

… and here is a reproduction demo.

……这是一个复制演示

回答by Eliran Malka



Quick-'n-Dirty it (Demo)

Quick-'n-Dirty it (演示)

We would want to make sure that all images are loaded, so let's write a directive for that:

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

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

… and attach it to the ng-src'd elements:

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

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

Now we can compare the number of events caught with our model, and act as needed:

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

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



Decouple it (Demo)

解耦它(演示)

This is a little disturbing, as it's not adhering to the Law of Demeter— any directive that will watch the $imageLoadedevent will have to know about the model (scope.videos.objects.length).

这有点令人不安,因为它不遵守迪米特法则——任何监视$imageLoaded事件的指令都必须了解模型 ( scope.videos.objects.length)。

We can prevent this coupling by finding out how many images were loaded without explicitly addressing the model. Let's assume that the events will be handled in an ng-repeat.

我们可以通过在不明确寻址模型的情况下找出加载了多少图像来防止这种耦合。假设事件将在ng-repeat.

  1. Make sure ng-repeathas finished, and fire an event with the items count. We can do that by attaching a controller with a sole purpose - to watch the $lastproperty. Once it's found (with a truthy value), we will fire an event to notify about it:

    .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. Now, catch the events and activate the slider initialization accordingly:

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

There, now our directive does not have to know about the model. But, it's a bit cumbersome, now we have to tie a directive anda controller.

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



Fold it (Demo)

折叠它(演示)

Let's extract this whole shabang into a single directive:

让我们将整个 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;
                }
            });
        }
    };
});

… which will be applied to the ng-repeated element:

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

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

Now we can simply watch $allImagesLoadede.g. in the slider:

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

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



Generalize it (if you want to) (Demo)

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

We can break it down again and apply this approach app-wide to use event dispatching for anyng-repeatfinish or ng-srcload, which is not always necessary (1), but can be quite useful. Let's see how:

我们可以再次分解它并在整个应用程序范围内应用这种方法来为任何ng-repeat完成或ng-src加载使用事件调度,这并不总是必要的( 1),但可能非常有用。让我们看看如何:

  1. Decorate the ng-srcdirective, so it sends an event when an image is loaded:

    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. Decorate ng-repeatto notify when it finishes:

    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. Now can catch the events anywhere, e.g. in your slider directive:

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

It seems to defeat the purpose, as we're back to square one, but it can be very powerful. And also — look, mommy, no new directives!

这似乎违背了目的,因为我们又回到了第一个方面,但它可能非常强大。还有——看,妈妈,没有新的指令!

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



Put a sock in it (Demo)

把袜子放进去(演示)


     TL;DR, just gimme tha demo ! ! !     
 


     TL;DR, just gimme tha demo ! ! !     
 





1. Decoration has to be considered carefully, as the result will be sending an event on each image loaded across the application.

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



Overriding the linkfunction will not be possible in version 1.3.x onwards.

link1.3.x 及以后的版本将无法覆盖该功能。