есть ли обратный вызов post render для директивы Angular JS?


139

Я только что получил свою директиву для добавления шаблона к его элементу следующим образом:

# CoffeeScript
.directive 'dashboardTable', ->
  controller: lineItemIndexCtrl
  templateUrl: "<%= asset_path('angular/templates/line_items/dashboard_rows.html') %>"
  (scope, element, attrs) ->
    element.parent('table#line_items').dataTable()
    console.log 'Just to make sure this is run'

# HTML
<table id="line_items">
    <tbody dashboard-table>
    </tbody>
</table>

Я также использую подключаемый модуль jQuery под названием DataTables. Обычно он используется так: $ ('table # some_id'). DataTable (). Вы можете передать данные JSON в вызов dataTable () для предоставления данных таблицы ИЛИ вы можете иметь данные уже на странице, и он сделает все остальное .. Я делаю последнее, имея строки уже на странице HTML .

Но проблема в том, что я должен вызвать dataTable () для таблицы # line_items ПОСЛЕ готовности DOM. Моя указанная выше директива вызывает метод dataTable () ПЕРЕД добавлением шаблона к элементу директивы. Есть ли способ вызвать функции ПОСЛЕ добавления?

Спасибо за помощь!

ОБНОВЛЕНИЕ 1 после ответа Энди:

Я хочу убедиться, что метод ссылки вызывается только ПОСЛЕ того, как все находится на странице, поэтому я изменил директиву для небольшого теста:

# CoffeeScript
#angular.module(...)
.directive 'dashboardTable', ->
    {
      link: (scope,element,attrs) -> 
        console.log 'Just to make sure this gets run'
        element.find('#sayboo').html('boo')

      controller: lineItemIndexCtrl
      template: "<div id='sayboo'></div>"

    }

И я действительно вижу "boo" в div # sayboo.

Затем я пробую вызов jquery datatable

.directive 'dashboardTable',  ->
    {
      link: (scope,element,attrs) -> 
        console.log 'Just to make sure this gets run'
        element.parent('table').dataTable() # NEW LINE

      controller: lineItemIndexCtrl
      templateUrl: "<%= asset_path('angular/templates/line_items/dashboard_rows.html') %>"
    }

Там не повезло

Затем я пытаюсь добавить тайм-аут:

.directive 'dashboardTable', ($timeout) ->
    {
      link: (scope,element,attrs) -> 
        console.log 'Just to make sure this gets run'
        $timeout -> # NEW LINE
          element.parent('table').dataTable()
        ,5000
      controller: lineItemIndexCtrl
      templateUrl: "<%= asset_path('angular/templates/line_items/dashboard_rows.html') %>"
    }

И это работает. Так что мне интересно, что не так в версии кода без таймера?


1
@adardesign Нет, я никогда не делал, мне пришлось использовать таймер. По какой-то причине обратный вызов здесь не является обратным вызовом. У меня есть таблица с 11 столбцами и сотнями строк, поэтому, естественно, angular выглядит неплохим выбором для привязки данных; но мне также нужно использовать плагин jquery Datatables, который так же прост, как $ ('table'). datatable (). Используя директиву или просто используйте тупой объект json со всеми строками и используйте ng-repeat для итерации, я не могу заставить мой $ (). Datatable () запускаться ПОСЛЕ визуализации элемента таблицы html, поэтому мой трюк в настоящее время заключается в таймере чтобы проверить, если $ ('tr'). length> 3 (b / c верхнего / нижнего колонтитула)
Ник Со,

2
@adardesign И да, я пробовал все методы компиляции, метод компиляции, возвращающий объект, содержащий методы postLink / preLink, метод компиляции, возвращающий только функцию (а именно функцию связывания), метод связывания (без метода компиляции, потому что, насколько я могу судить, если у вас есть метод компиляции, который возвращает метод связывания, функция связывания игнорируется) .. Ни один из них не работал, поэтому приходится полагаться на старый добрый $ timeout. Обновлю этот пост, если найду что-нибудь, что работает лучше, или просто когда обнаружу, что обратный вызов действительно действует как обратный вызов
Ник Со

Ответы:


215

Если второй параметр, «задержка», не указан, по умолчанию функция выполняется после того, как DOM завершит рендеринг. Поэтому вместо setTimeout используйте $ timeout:

$timeout(function () {
    //DOM has finished rendering
});

8
Почему это не объясняется в документации ?
Gaui

23
Вы правы, мой ответ немного вводит в заблуждение, потому что я попытался сделать его простым. Полный ответ заключается в том, что этот эффект является результатом не Angular, а браузера. $timeout(fn)в конечном итоге вызывает, setTimeout(fn, 0)что приводит к прерыванию выполнения Javascript и позволяет браузеру сначала отображать контент, прежде чем продолжить выполнение этого Javascript.
парламент

7
Думайте о браузере как о добавлении в очередь определенных задач, таких как «выполнение javascript» и «рендеринг DOM» по отдельности, и то, что setTimeout (fn, 0) выталкивает текущее «выполнение javascript» в конец очереди после отрисовки .
парламент

2
@GabLeRoux да, это будет иметь тот же эффект, за исключением того, что $ timeout имеет дополнительное преимущество вызова $ scope. $ Apply () после его запуска. С _.defer () вам нужно будет вызвать его вручную, если myFunction изменяет переменные в области видимости.
парламент

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

14

У меня была такая же проблема, и я считаю, что ответ на самом деле отрицательный. См . Комментарий Мишко и некоторые обсуждения в группе .

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

Мы решили это эвристически с помощью setTimeout, как и вы.

(Имейте в виду, что не все со мной согласны - вы должны прочитать комментарии по ссылкам выше и узнать, что вы думаете.)


7

Вы можете использовать функцию «ссылка», также известную как postLink, которая запускается после того, как шаблон вставлен.

app.directive('myDirective', function() {
  return {
    link: function(scope, elm, attrs) { /*I run after template is put in */ },
    template: '<b>Hello</b>'
  }
});

Прочтите это, если вы планируете делать директивы, это большая помощь: http://docs.angularjs.org/guide/directive


Привет, Энди, большое спасибо за ответ; Я пробовал использовать функцию ссылки, но я бы не прочь повторить попытку, точно так же, как вы ее кодируете; Я провел последние 1,5 дня, читая эту директивную страницу; а также посмотрите примеры на сайте angular. Сейчас попробую ваш код.
Nik So

А, теперь я вижу, что вы пытались установить ссылку, но делали это неправильно. Если вы просто возвращаете функцию, предполагается, что это ссылка. Если вы возвращаете объект, вы должны вернуть его с ключом как «ссылка». Вы также можете вернуть функцию связывания из своей функции компиляции.
Эндрю Джослин

Привет, Энди, получил мои результаты; Я почти потерял рассудок, потому что действительно сделал то, что вы здесь ответили. Пожалуйста, посмотрите мое обновление
Nik So

Хммм, попробуйте что-нибудь вроде: <table id = "bob"> <tbody dashboard-table = "# bob"> </tbody> </table> Затем в своей ссылке выполните $ (attrs.dashboardTable) .dataTable (), чтобы убедитесь, что он выбран правильно. Или я думаю, вы уже пробовали это .. Я действительно не уверен, что ссылка не работает.
Эндрю Джослин

Это один работал для меня, я хотел , чтобы переместить элементы в йоте после рендеринга шаблона для моего требования, сделал это в ссылке function.Thanks
абхи

7

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

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

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

HTML:

<div my-directive my-directive-watch="!!myContent">{{myContent}}</div>

JS:

app.directive('myDirective', [ function(){
    return {
        restrict : 'A',
        scope : {
            myDirectiveWatch : '='
        },
        compile : function(){
            return {
                post : function(scope, element, attributes){

                    scope.$watch('myDirectiveWatch', function(newVal, oldVal){
                        if (newVal !== oldVal) {
                            // Do stuff ...
                        }
                    });

                }
            }
        }
    }
}]);

Примечание. Вместо того, чтобы просто преобразовывать переменную myContent в bool в атрибуте my-directive-watch, можно представить себе любое произвольное выражение.

Примечание. Изолировать область видимости, как в приведенном выше примере, можно только один раз для каждого элемента - попытка сделать это с несколькими директивами для одного и того же элемента приведет к ошибке $ compile: multidir - см .: https://docs.angularjs.org / error / $ compile / multidir


7

Возможно, я поздно отвечу на этот вопрос. Но все же кому-то мой ответ может принести пользу.

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

В моем контроллере:

$scope.render=false;

//this will fire after load the the page

angular.element(document).ready(function() {
    $scope.render=true;
});

В моем html (в моем случае компонент html - это холст)

<canvas ng-if="render"> </canvas>

3

У меня была такая же проблема, но с использованием Angular + DataTable с fnDrawCallback+ группировкой строк + $ скомпилированными вложенными директивами. Я поместил тайм-аут $ в свою fnDrawCallbackфункцию, чтобы исправить рендеринг разбивки на страницы.

Перед примером на основе источника row_grouping:

var myDrawCallback = function myDrawCallbackFn(oSettings){
  var nTrs = $('table#result>tbody>tr');
  for(var i=0; i<nTrs.length; i++){
     //1. group rows per row_grouping example
     //2. $compile html templates to hook datatable into Angular lifecycle
  }
}

После примера:

var myDrawCallback = function myDrawCallbackFn(oSettings){
  var nTrs = $('table#result>tbody>tr');
  $timeout(function requiredRenderTimeoutDelay(){
    for(var i=0; i<nTrs.length; i++){
       //1. group rows per row_grouping example
       //2. $compile html templates to hook datatable into Angular lifecycle
    }
  ,50); //end $timeout
}

Даже небольшой задержки тайм-аута было достаточно, чтобы Angular смог отобразить мои скомпилированные директивы Angular.


Просто любопытно, а у вас довольно большая таблица с множеством столбцов? потому что я обнаружил, что мне нужно раздражающее количество миллисекунд (> 100), чтобы не дать вызову dataTable () подавиться
Ник Со

Я обнаружил, что проблема возникла при навигации по странице DataTable для наборов результатов от 2 до более 150 строк. Итак, нет - я не думаю, что размер таблицы был проблемой, но, возможно, DataTable добавил достаточно накладных расходов на рендеринг, чтобы проглотить некоторые из этих миллисекунд. Я сосредоточился на том, чтобы группировка строк работала в DataTable с минимальной интеграцией AngularJS.
JJ Zabkar 05

2

Ни одно из решений не помогло мне принять использование тайм-аута. Это потому, что я использовал шаблон, который динамически создавался во время postLink.

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

Обратитесь к этому: http://blog.brunoscopelliti.com/run-a-directive-after-the-dom-has-finished-rendering


0

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

define(['angular'], function (angular) {
  'use strict';
  return angular.module('app.common.after-render', [])
    .directive('afterRender', [ '$timeout', function($timeout) {
    var def = {
        restrict : 'A', 
        terminal : true,
        transclude : false,
        link : function(scope, element, attrs) {
            if (attrs) { scope.$eval(attrs.afterRender) }
            scope.$emit('onAfterRender')
        }
    };
    return def;
    }]);
});

тогда вы можете сделать:

<div after-render></div>

или с любым полезным выражением, например:

<div after-render="$emit='onAfterThisConcreteThingRendered'"></div>


На самом деле это не происходит после рендеринга контента. Если бы у меня было выражение внутри элемента <div after-render> {{blah}} </div>, на этом этапе выражение еще не оценивалось. Содержимое div все еще {{blah}} внутри функции ссылки. Так что технически вы запускаете событие до того, как будет отрисован контент.
Эдвард Оламисан,

Это неглубокое действие после рендеринга, я никогда не утверждал, что он глубокий,
Себастьян Састре,

0

У меня это работает со следующей директивой:

app.directive('datatableSetup', function () {
    return { link: function (scope, elm, attrs) { elm.dataTable(); } }
});

И в HTML:

<table class="table table-hover dataTable dataTable-columnfilter " datatable-setup="">

устранение неполадок, если вышеуказанное не работает для вас.

1) обратите внимание, что datatableSetup является эквивалентом datatable-setup. Angular меняет формат на верблюжий футляр.

2) убедитесь, что приложение определено до директивы. например, простое определение приложения и директива.

var app = angular.module('app', []);
app.directive('datatableSetup', function () {
    return { link: function (scope, elm, attrs) { elm.dataTable(); } }
});

0

Поскольку невозможно предвидеть порядок загрузки, можно использовать простое решение.

Давайте посмотрим на отношения директива-«пользователь директивы». Обычно пользователь директивы передает некоторые данные в директиву или использует некоторые функциональные возможности (функции), которые она предоставляет. Директива, с другой стороны, ожидает, что некоторые переменные будут определены в ее области действия.

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

А теперь директива:

app.directive('aDirective', function () {
    return {
        scope: {
            input: '=',
            control: '='
        },
        link: function (scope, element) {
            function functionThatNeedsInput(){
                //use scope.input here
            }
            if ( scope.input){ //We already have input 
                functionThatNeedsInput();
            } else {
                scope.control.init = functionThatNeedsInput;
            }
          }

        };
})

а теперь пользователь директивы html

<a-directive control="control" input="input"></a-directive>

и где-то в контроллере компонента, использующего директиву:

$scope.control = {};
...
$scope.input = 'some data could be async';
if ( $scope.control.functionThatNeedsInput){
    $scope.control.functionThatNeedsInput();
}

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

Используя наш сайт, вы подтверждаете, что прочитали и поняли нашу Политику в отношении файлов cookie и Политику конфиденциальности.
Licensed under cc by-sa 3.0 with attribution required.