Вызов функции после завершения ng-repeat



то, что я пытаюсь реализовать, в основном является обработчиком "on ng repeat finished rendering". Я могу определить, когда это делается, но я не могу понять, как вызвать функцию из него.



Проверьте скрипку:http://jsfiddle.net/paulocoelho/BsMqq/3/



JS



var module = angular.module('testApp', [])
.directive('onFinishRender', function () {
return {
restrict: 'A',
link: function (scope, element, attr) {
if (scope.$last === true) {
element.ready(function () {
console.log("calling:"+attr.onFinishRender);
// CALL TEST HERE!
});
}
}
}
});

function myC($scope) {
$scope.ta = [1, 2, 3, 4, 5, 6];
function test() {
console.log("test executed");
}
}


HTML



<div ng-app="testApp" ng-controller="myC">
<p ng-repeat="t in ta" on-finish-render="test()">{{t}}</p>
</div>


ответ:
Рабочая скрипка от finishingmove:http://jsfiddle.net/paulocoelho/BsMqq/4/

684   9  

9 ответов:

var module = angular.module('testApp', [])
    .directive('onFinishRender', function ($timeout) {
    return {
        restrict: 'A',
        link: function (scope, element, attr) {
            if (scope.$last === true) {
                $timeout(function () {
                    scope.$emit(attr.onFinishRender);
                });
            }
        }
    }
});

обратите внимание, что я не использовал .ready() но скорее завернул его в $timeout. $timeout удостоверяется, что он выполняется, когда ng-повторяющиеся элементы действительно закончили рендеринг (потому что $timeout будет выполняться в конце текущего цикла дайджеста -- и он также будет называть $apply внутренне, в отличие от setTimeout). Так что после ng-repeat закончил, мы используем $emit для передачи события во внешние области (родственные и родительские области).

а потом в вашем контроллер, вы можете поймать его с $on:

$scope.$on('ngRepeatFinished', function(ngRepeatFinishedEvent) {
    //you also get the actual event object
    //do stuff, execute functions -- whatever...
});

С HTML, который выглядит примерно так:

<div ng-repeat="item in items" on-finish-render="ngRepeatFinished">
    <div>{{item.name}}}<div>
</div>

использовать $evalAsync если вы хотите, чтобы ваш обратный вызов (т. е. test()) выполнялся после построения DOM, но до визуализации браузера. Это предотвратит мерцание -- ref.

if (scope.$last) {
   scope.$evalAsync(attr.onFinishRender);
}

Скрипка.

Если вы действительно хотите вызвать свой обратный вызов после рендеринга, используйте $timeout:

if (scope.$last) {
   $timeout(function() { 
      scope.$eval(attr.onFinishRender);
   });
}

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

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

и если вам нужно получать уведомления каждый раз, когда ng-repeat получает повторно вынесено и не только в первый раз, я нашел способ сделать это, это довольно "hacky", но он будет работать нормально, если вы знаете, что вы делаете. Используйте это $filter в своем ng-repeatперед использованием любого другого $filter:

.filter('ngRepeatFinish', function($timeout){
    return function(data){
        var me = this;
        var flagProperty = '__finishedRendering__';
        if(!data[flagProperty]){
            Object.defineProperty(
                data, 
                flagProperty, 
                {enumerable:false, configurable:true, writable: false, value:{}});
            $timeout(function(){
                    delete data[flagProperty];                        
                    me.$emit('ngRepeatFinished');
                },0,false);                
        }
        return data;
    };
})

это $emit мероприятие под названием ngRepeatFinished каждый раз ng-repeat отрисовывается.

как использовать:

<li ng-repeat="item in (items|ngRepeatFinish) | filter:{name:namedFiltered}" >

The ngRepeatFinish фильтр должен быть применен непосредственно к Array или Object определена в $scope, вы можете применить и другие фильтры после.

как не использовать:

<li ng-repeat="item in (items | filter:{name:namedFiltered}) | ngRepeatFinish" >

сначала не применяйте другие фильтры, а затем применяйте ngRepeatFinish фильтр.

когда я должен использовать это?

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

что не делать в функции, которая обрабатывает ngRepeatFinished событие:

  • не выполнять $scope.$apply в этой функции, или вы поставите угловой в бесконечный цикл, что угловые не сможет обнаружить.

  • не используйте его для внесения изменений в $scope свойства, потому что эти изменения не будут отражены в вашем представлении до следующего $digest цикл, и так как вы не можете выполнить $scope.$apply они не будут любое использование.

но фильтры не предназначены для такого использования!!"

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

подводя итоги

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

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

директива:

var module = angular.module('testApp', [])
    .directive('onFinishRender', function ($timeout) {
    return {
        restrict: 'A',
        link: function (scope, element, attr) {
            if (scope.$last === true) {
            $timeout(function () {
                scope.$emit(attr.broadcasteventname ? attr.broadcasteventname : 'ngRepeatFinished');
            });
            }
        }
    }
});

в вашем контроллере, поймать события с $on:

$scope.$on('ngRepeatBroadcast1', function(ngRepeatFinishedEvent) {
// Do something
});

$scope.$on('ngRepeatBroadcast2', function(ngRepeatFinishedEvent) {
// Do something
});

в вашем шаблоне с несколькими ng-repeat

<div ng-repeat="item in collection1" on-finish-render broadcasteventname="ngRepeatBroadcast1">
    <div>{{item.name}}}<div>
</div>

<div ng-repeat="item in collection2" on-finish-render broadcasteventname="ngRepeatBroadcast2">
    <div>{{item.name}}}<div>
</div>

другие решения будут работать нормально при начальной загрузке страницы, но вызов $timeout из контроллера является единственным способом гарантировать, что ваша функция вызывается при изменении модели. Вот такой рабочий скрипка который использует $ timeout. Для вашего примера это будет:

.controller('myC', function ($scope, $timeout) {
$scope.$watch("ta", function (newValue, oldValue) {
    $timeout(function () {
       test();
    });
});

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

Если вы не прочь использовать двухдолларовые реквизиты области видимости, и вы пишете директиву, единственным содержанием которой является повторение, есть довольно простое решение (при условии, что вы заботитесь только о начальном рендеринге). В функции ссылки:

const dereg = scope.$watch('$$childTail.$last', last => {
    if (last) {
        dereg();
        // do yr stuff -- you may still need a $timeout here
    }
});

это полезно для случаев, когда у вас есть директива, которая должна делать DOM manip на основе ширины или высоты членов отображаемого списка (что, я думаю, является наиболее вероятной причиной, по которой можно было бы задать этот вопрос), но это не так универсальный, как и другие решения, которые были предложены.

решение этой проблемы с фильтрованным ngRepeat могло бы быть с мутация события, но они устарели (без немедленной замены).

затем я подумал о другом просто:

app.directive('filtered',function($timeout) {
    return {
        restrict: 'A',link: function (scope,element,attr) {
            var elm = element[0]
                ,nodePrototype = Node.prototype
                ,timeout
                ,slice = Array.prototype.slice
            ;

            elm.insertBefore = alt.bind(null,nodePrototype.insertBefore);
            elm.removeChild = alt.bind(null,nodePrototype.removeChild);

            function alt(fn){
                fn.apply(elm,slice.call(arguments,1));
                timeout&&$timeout.cancel(timeout);
                timeout = $timeout(altDone);
            }

            function altDone(){
                timeout = null;
                console.log('Filtered! ...fire an event or something');
            }
        }
    };
});

это подключается к узлу.прототипные методы родительского элемента с одним тиком $timeout для наблюдения за последовательными изменениями.

Он работает в основном правильно, но я получил некоторые случаи, когда altDone будет называться дважды.

снова... добавьте эту директиву в родитель в ngRepeat.

очень легко, вот как я это сделал.

.directive('blockOnRender', function ($blockUI) {
    return {
        restrict: 'A',
        link: function (scope, element, attrs) {
            
            if (scope.$first) {
                $blockUI.blockElement($(element).parent());
            }
            if (scope.$last) {
                $blockUI.unblockElement($(element).parent());
            }
        }
    };
})

пожалуйста, взгляните на скрипку,http://jsfiddle.net/yNXS2/. так как Директива, которую вы создали, не создала новую область, я продолжил путь.

$scope.test = function(){... Это случилось.

Comments

    Ничего не найдено.