AngularJS - Манипулирование DOM после ng-repeat завершено

У меня возникают некоторые проблемы, связанные с манипуляцией DOM после обработки данных.

У нас есть плагин слайдера jQuery, привязанный к данным и работающий нормально, но при использовании ng-repeat мы должны завернуть его инициализацию $timeout, чтобы он работал — и теперь это даже не работает.

Я думаю, что использование $timeout ненадежно, что приводит к плохому исправлению. В jQuery я мог бы использовать $(document).ready() — который был твердым, но использование angular.element(document).ready() тоже не работает.

Вызывается указатель слайдера, но не может получить высоту изображений в слайдере, потому что изображения не были загружены в DOM — в результате чего ползунок имеет рассчитанную высоту 0.

В настоящее время я нахожу это очень расстраивающим - должен быть способ манипулировать DOM после того, как данные (например, в ng-repeat) прошли циклический переход.

Инициализация ползунка выполняется следующим образом:

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

& hellip; и вот демонстрация .

Ответ 1


Quick -n-Dirty it (демонстрация)

Мы хотели бы убедиться, что все изображения загружены, поэтому напишите директиву для этого:

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

& hellip; и присоедините его к элементам 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!
    }
});


Разделить его (демо)

Это немного беспокоит, так как он не придерживается Закона Деметры — любая директива, которая будет смотреть событие $imageLoaded, должна знать о модели (scope.videos.objects.length).

Мы можем предотвратить эту связь, выяснив, сколько изображений было загружено без явной адресации модели. Предположим, что события будут обрабатываться в ng-repeat.

  • Удостоверьтесь, что 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">
    
  • Теперь поймайте события и активируйте инициализацию ползунка соответственно:

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

& hellip; который будет применен к элементу ng-repeat ed:

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

Теперь мы можем просто смотреть $allImagesLoaded, например. в слайдере:

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


Обобщите его (если хотите) (демо)

Мы можем снова сломать его и применить этот подход в приложении для использования диспетчеризации событий для любой загрузки ng-repeat или ng-src, что не всегда необходимо (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;
        });
        // ...
    });
    
  • Украсьте 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;
        });
    });
    
  • Теперь можно поймать события в любом месте, например. в директиве ползунка:

    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. Украшение должно быть тщательно рассмотрено, так как в результате будет отправлено событие на каждое изображение, загруженное через приложение.


& bull; Переопределение функции link будет невозможно в версии 1.3.x и далее.