финишное событие ng-repeat

207

Я хочу вызвать некоторую функцию jQuery, нацеленную на div с таблицей. Эта таблица заполнена ng-repeat.

Когда я звоню на

$(document).ready()

У меня нет результата.

Также

$scope.$on('$viewContentLoaded', myFunc);

не помогает

Есть ли способ выполнить функцию сразу после завершения заполнения ng-repeat? Я прочитал совет по использованию custom directive, но я понятия не имею, как использовать его с ng-repeat и моим div ...

ChruS
источник

Ответы:

241

Действительно, вы должны использовать директивы, и нет никакого события, связанного с концом цикла ng-Repeat (так как каждый элемент создается индивидуально и имеет свое собственное событие). Но а) использование директив может быть всем, что вам нужно, и б) есть несколько специфических свойств ng-Repeat, которые вы можете использовать, чтобы сделать ваше событие "on ngRepeat закончено".

В частности, если все, что вам нужно, это стилизовать / добавить события ко всей таблице, вы можете сделать это, используя директиву, которая охватывает все элементы ngRepeat. С другой стороны, если вы хотите обратиться к каждому элементу отдельно, вы можете использовать директиву внутри ngRepeat, и она будет действовать на каждый элемент после его создания.

Тогда, Есть $index, $first, $middleи $lastсвойства , которые можно использовать для запуска событий. Итак, для этого HTML:

<div ng-controller="Ctrl" my-main-directive>
  <div ng-repeat="thing in things" my-repeat-directive>
    thing {{thing}}
  </div>
</div>

Вы можете использовать директивы так:

angular.module('myApp', [])
.directive('myRepeatDirective', function() {
  return function(scope, element, attrs) {
    angular.element(element).css('color','blue');
    if (scope.$last){
      window.alert("im the last!");
    }
  };
})
.directive('myMainDirective', function() {
  return function(scope, element, attrs) {
    angular.element(element).css('border','5px solid red');
  };
});

Посмотрите это в действии в этом Plunker . Надеюсь, поможет!

Тьяго Рольдао
источник
Могу ли я заставить myMainDirectiveкод выполняться только после завершения цикла? Мне нужно обновить полосу прокрутки родительского div.
ChruS
2
Конечно! Просто замените "window.alert" на функцию запуска событий и перехватите ее с помощью главной директивы. Я обновил де Плункер, чтобы сделать это, в качестве примера.
Тьяго Рольдао
9
Рекомендуется сначала включить библиотеку jQuery (до Angular). Это приведет к тому, что все элементы Angular будут обернуты с помощью jQuery (вместо jqLite). Тогда вместо angular.element (element) .css () и $ (element) .children (). Css () вы можете просто написать element.css () и element.children (). Css (). См. Также groups.google.com/d/msg/angular/6A3Skwm59Z4/oJ0WhKGAFK0J
Марк Райкок,
11
Any1 есть идеи, как заставить это работать для последующих рендеров в директиве ngRepeat? то есть: ссылка
RavenHursT
1
Это соглашение об именах для всех угловых директив. См. Docs.angularjs.org/guide/directive#normalization
Тиаго Рольдао
68

Если вы просто хотите выполнить некоторый код в конце цикла, вот несколько более простой вариант, который не требует дополнительной обработки событий:

<div ng-controller="Ctrl">
  <div class="thing" ng-repeat="thing in things" my-post-repeat-directive>
    thing {{thing}}
  </div>
</div>
function Ctrl($scope) {
  $scope.things = [
    'A', 'B', 'C'  
  ];
}

angular.module('myApp', [])
.directive('myPostRepeatDirective', function() {
  return function(scope, element, attrs) {
    if (scope.$last){
      // iteration is complete, do whatever post-processing
      // is necessary
      element.parent().css('border', '1px solid black');
    }
  };
});

Посмотрите живую демонстрацию.

karlgold
источник
Это работает, если я использую контроллер как синтаксис? Пожалуйста? В нет, есть ли другой способ, который работает с контроллером, как?
Дэн Нгуен,
63

Нет необходимости создавать директиву, особенно просто для ng-repeatзавершения события.

ng-init делает магию для вас.

  <div ng-repeat="thing in things" ng-init="$last && finished()">

он $lastгарантирует, что finishedсрабатывает только тогда, когда последний элемент был представлен в DOM.

Не забудьте создать $scope.finishedсобытие.

Удачного кодирования!

РЕДАКТИРОВАТЬ: 23 октября 2016

Если вы также хотите вызвать finishedфункцию, когда в массиве нет элемента, вы можете использовать следующий обходной путь

<div style="display:none" ng-init="things.length < 1 && finished()"></div>
//or
<div ng-if="things.length > 0" ng-init="finished()"></div>

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

Например

<div ng-if="things.length > 0" ng-init="finished()"></div>
<div ng-repeat="thing in things" ng-init="$last && finished()">
Викас Бансал
источник
Что если «вещи» окажутся пустым массивом?
Фрэн Поляк
1
@FranePoljak Я добавил решение в ответ.
Викас Бансал
1
В случае пустого массива обработайте его в контроллере
JSAddict
Исправьте ответ Vikas Bansal: // используйте первый div, если хотите проверить, не имеет ли массив никакого значения, и соответственно вызовите функцию. <div ng-if = "! things.length" ng-hide = "true" ng-init = "законченный ()"> </ div> <div ng-repeat = "вещь в вещах" ng-init = "$ last &&
Алекс Тесленко
41

Вот повторяющаяся директива, которая вызывает указанную функцию, когда истина. Я обнаружил, что вызываемая функция должна использовать $ timeout с interval = 0 перед выполнением манипуляций с DOM, таких как инициализация всплывающих подсказок на визуализированных элементах. jsFiddle: http://jsfiddle.net/tQw6w/

В $ scope.layoutDone попробуйте закомментировать строку $ timeout и раскомментировать «НЕ ПРАВИЛЬНО!» линия, чтобы увидеть разницу во всплывающих подсказках.

<ul>
    <li ng-repeat="feed in feedList" repeat-done="layoutDone()" ng-cloak>
    <a href="{{feed}}" title="view at {{feed | hostName}}" data-toggle="tooltip">{{feed | strip_http}}</a>
    </li>
</ul>

JS:

angular.module('Repeat_Demo', [])

    .directive('repeatDone', function() {
        return function(scope, element, attrs) {
            if (scope.$last) { // all are rendered
                scope.$eval(attrs.repeatDone);
            }
        }
    })

    .filter('strip_http', function() {
        return function(str) {
            var http = "http://";
            return (str.indexOf(http) == 0) ? str.substr(http.length) : str;
        }
    })

    .filter('hostName', function() {
        return function(str) {
            var urlParser = document.createElement('a');
            urlParser.href = str;
            return urlParser.hostname;
        }
    })

    .controller('AppCtrl', function($scope, $timeout) {

        $scope.feedList = [
            'http://feeds.feedburner.com/TEDTalks_video',
            'http://feeds.nationalgeographic.com/ng/photography/photo-of-the-day/',
            'http://sfbay.craigslist.org/eng/index.rss',
            'http://www.slate.com/blogs/trending.fulltext.all.10.rss',
            'http://feeds.current.com/homepage/en_US.rss',
            'http://feeds.current.com/items/popular.rss',
            'http://www.nytimes.com/services/xml/rss/nyt/HomePage.xml'
        ];

        $scope.layoutDone = function() {
            //$('a[data-toggle="tooltip"]').tooltip(); // NOT CORRECT!
            $timeout(function() { $('a[data-toggle="tooltip"]').tooltip(); }, 0); // wait...
        }

    })
Джозеф Остер
источник
Я не хотел использовать тайм-аут в $, но когда вы сказали, что он может быть установлен в 0, я решил попробовать, и это сработало хорошо. Интересно, если это вопрос переваривания, и есть ли способ сделать то, что делает $ timeout без использования $ timeout.
Джамила Хук
Не могли бы вы объяснить, почему это работает? Я пытаюсь получить доступ к динамическим идентификаторам, и это работает только после тайм-аута с задержкой 0. Я видел это «хакерское» решение в нескольких постах, но никто не объясняет, почему оно работает.
Джин
30

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

<div ng-controller="MyCtrl">
    <div ng-repeat="thing in things">
        thing: {{ thing }}
    </div>
    <div ng-init="fireEvent()"></div>
</div>

myModule.controller('MyCtrl', function($scope, $timeout){
    $scope.things = ['A', 'B', 'C'];

    $scope.fireEvent = function(){

        // This will only run after the ng-repeat has rendered its things to the DOM
        $timeout(function(){
            $scope.$broadcast('thingsRendered');
        }, 0);

    };
});

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

dshap
источник
1
Прекрасно, это сделало работу. Я хотел автоматически выбрать текст в текстовом поле, и таймаут сделал свое дело. В противном случае текст {{model.value}} будет выделен, а затем отменен при вводе привязанного к данным model.value.
JoshGough
27

В дополнение к ответу Павла , что-то более читаемое и легко понятное будет:

<ul>
    <li ng-repeat="item in items" 
        ng-init="$last ? doSomething() : angular.noop()">{{item}}</li>
</ul>

Почему еще вы думаете, angular.noopесть ли в первую очередь ...?

Преимущества:

Вам не нужно писать директиву для этого ...

deostroll
источник
20
Зачем использовать троичный, если вы можете использовать логическую логику:ng-init="$last && doSomething( )"
Нейт Уиттакер
Его легче читать @NateWhittaker (и его сложнее читать, чем троичный оператор, впечатляет). Хорошо иметь чистый и понятный код
Джастин
21

Может быть, немного более простой подход с методом ngInitЛодаша debounceбез необходимости специальной директивы:

контроллер:

$scope.items = [1, 2, 3, 4];

$scope.refresh = _.debounce(function() {
    // Debounce has timeout and prevents multiple calls, so this will be called 
    // once the iteration finishes
    console.log('we are done');
}, 0);

Шаблон:

<ul>
    <li ng-repeat="item in items" ng-init="refresh()">{{item}}</li>
</ul>

Обновить

Существует еще более простое решение AngularJS, использующее троичный оператор:

Шаблон:

<ul>
    <li ng-repeat="item in items" ng-init="$last ? doSomething() : null">{{item}}</li>
</ul>

Имейте в виду, что ngInit использует фазу предварительной компиляции - то есть выражение вызывается до обработки дочерних директив. Это означает, что все еще может потребоваться асинхронная обработка.

Павел Хорал
источник
Следует заметить, что вам нужен lodash.js, чтобы это работало :) Также: часть ", 20" в $ scope.refresh =, это число в миллисекундах до вызова этого метода после последнего изменения?
Йорген Скар Фишер
Добавлено Lodash перед противодребезговой ссылки. Да, 20 - задержка перед выполнением метода - изменено на 0, поскольку это не имеет значения, пока вызов асинхронный.
Павел Хорал
1
Также подумайте об этом, так как троичный оператор разрешен в выражениях, простой ng-init="$last ? refresh() : false"будет также работать. И это даже не требует Лодаш.
Павел Хорал
<div ng-repeat = "i in [1,2,4]" ng-init = "doSomething ($ last)"> </ div> $ scope.doSomething = function (lastElement) {if (lastElem) бла-бла-бла }
JSAddict
4

Это также может быть необходимо, когда вы проверяете scope.$lastпеременную, чтобы обернуть ваш триггер с помощью setTimeout(someFn, 0). A setTimeout 0- это общепринятая техника в javascript, и я directiveдолжен был правильно работать.

Cody
источник
Я нашел ту же проблему. как в backbone + angular, если вам нужно что-то вроде чтения значений свойств css, примененных к элементу DOM, я не смог бы найти способ без использования тайм-аута.
Чови
3

Это улучшение идей, выраженных в других ответах, чтобы показать, как получить доступ к свойствам ngRepeat ($ index, $ first, $ middle, $ last, $ even, $ odd) при использовании декларативного синтаксиса и изолировать область видимости ( Google рекомендовал лучшие практики) с директивой элемента. Обратите внимание на основную разницу: scope.$parent.$last.

angular.module('myApp', [])
.directive('myRepeatDirective', function() {
  return {
    restrict: 'E',
    scope: {
      someAttr: '='
    },
    link: function(scope, element, attrs) {
      angular.element(element).css('color','blue');
      if (scope.$parent.$last){
        window.alert("im the last!");
      }
    }
  };
});
JoshuaDavid
источник
Не лучше ли использовать директивы в качестве атрибутов, а не элементов? т.е. ограничить: «А»?
Теджасви Хегде
2
Цель состояла в том, чтобы показать декларативный синтаксис + изолировать область видимости ... да, вы можете использовать директиву атрибута здесь, если хотите. Есть время и место для любого из них в зависимости от вашей цели.
Джошуа Дэвид
3

Я сделал это таким образом.

Создать директиву

function finRepeat() {
    return function(scope, element, attrs) {
        if (scope.$last){
            // Here is where already executes the jquery
            $(document).ready(function(){
                $('.materialboxed').materialbox();
                $('.tooltipped').tooltip({delay: 50});
            });
        }
    }
}

angular
    .module("app")
    .directive("finRepeat", finRepeat);

После того, как вы добавите его на этикетке, где этот нг-повтор

<ul>
    <li ng-repeat="(key, value) in data" fin-repeat> {{ value }} </li>
</ul>

И готово с этим будет запускаться в конце ng-repeat.

Mvochoa
источник
3
<div ng-repeat="i in items">
        <label>{{i.Name}}</label>            
        <div ng-if="$last" ng-init="ngRepeatFinished()"></div>            
</div>

Моим решением было добавить div для вызова функции, если элемент был последним в повторении.

scotty3
источник
2

Я хотел бы добавить еще один ответ, так как предыдущие ответы предполагают, что код, необходимый для запуска после выполнения ngRepeat, является угловым кодом, который в этом случае все ответы выше дают отличное и простое решение, некоторые более общие, чем другие, и в случае, если важна стадия жизненного цикла дайджеста, вы можете посмотреть в блоге Бена Наделя об этом, за исключением использования $ parse вместо $ eval.

но по моему опыту, как утверждает OP, он обычно запускает некоторые плагины или методы JQuery на скомпилированном DOM Finnaly, и в этом случае я обнаружил, что самое простое решение - создать директиву с setTimeout, так как функция setTimeout выдвигается до конца очереди браузера, это всегда сразу после того, как все сделано в угловых, обычно ngReapet, который продолжается после того, как его родительская функция postLinking

angular.module('myApp', [])
.directive('pluginNameOrWhatever', function() {
  return function(scope, element, attrs) {        
    setTimeout(function doWork(){
      //jquery code and plugins
    }, 0);        
  };
});

для тех, кто интересуется, что в таком случае, почему бы не использовать $ timeout, он вызывает другой цикл дайджеста, который совершенно не нужен

bresleveloper
источник
1

Мне пришлось рендерить формулы, используя MathJax после окончания ng-repeat, ни один из приведенных выше ответов не решил мою проблему, поэтому я сделал как показано ниже. Это не очень хорошее решение , но у меня получилось ...

<div ng-repeat="formula in controller.formulas">
    <div>{{formula.string}}</div>
    {{$last ? controller.render_formulas() : ""}}
</div>
Жоао Пауло
источник
0

Я нашел ответ здесь хорошо отработанным, но все же необходимо было добавить задержку

Создайте следующую директиву:

angular.module('MyApp').directive('emitLastRepeaterElement', function() {
return function(scope) {
    if (scope.$last){
        scope.$emit('LastRepeaterElement');
    }
}; });

Добавьте его к вашему повторителю как атрибут, например так:

<div ng-repeat="item in items" emit-last-repeater-element></div>

По словам Раду,

$scope.eventoSelecionado.internamento_evolucoes.forEach(ie => {mycode});

Для меня это работает, но мне все еще нужно добавить setTimeout

$scope.eventoSelecionado.internamento_evolucoes.forEach(ie => {
setTimeout(function() { 
    mycode
}, 100); });
Blomersi
источник
-4

Если вы просто хотите изменить имя класса, чтобы оно отображалось по-разному, приведенный ниже код поможет.

<div>
<div ng-show="loginsuccess" ng-repeat="i in itemList">
    <div id="{{i.status}}" class="{{i.status}}">
        <div class="listitems">{{i.item}}</div>
        <div class="listitems">{{i.qty}}</div>
        <div class="listitems">{{i.date}}</div>
        <div class="listbutton">
            <button ng-click="UpdateStatus(i.$id)" class="btn"><span>Done</span></button>
            <button ng-click="changeClass()" class="btn"><span>Remove</span></button>
        </div>
    <hr>
</div>

Этот код работал для меня, когда у меня было похожее требование отобразить товар в моем списке покупок шрифтом Strick Trough.

user4808836
источник