Я только что получил свою директиву для добавления шаблона к его элементу следующим образом:
# 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') %>"
}
И это работает. Так что мне интересно, что не так в версии кода без таймера?
Ответы:
Если второй параметр, «задержка», не указан, по умолчанию функция выполняется после того, как DOM завершит рендеринг. Поэтому вместо setTimeout используйте $ timeout:
$timeout(function () { //DOM has finished rendering });
источник
$timeout(fn)
в конечном итоге вызывает,setTimeout(fn, 0)
что приводит к прерыванию выполнения Javascript и позволяет браузеру сначала отображать контент, прежде чем продолжить выполнение этого Javascript.У меня была такая же проблема, и я считаю, что ответ на самом деле отрицательный. См . Комментарий Мишко и некоторые обсуждения в группе .
Angular может отслеживать, что все вызовы функций, которые он выполняет для управления DOM, завершены, но поскольку эти функции могут запускать асинхронную логику, которая все еще обновляет DOM после их возврата, Angular не ожидал, что узнает об этом. Любой обратный вызов, который дает Angular, иногда может работать, но на него небезопасно полагаться.
Мы решили это эвристически с помощью setTimeout, как и вы.
(Имейте в виду, что не все со мной согласны - вы должны прочитать комментарии по ссылкам выше и узнать, что вы думаете.)
источник
Вы можете использовать функцию «ссылка», также известную как 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
источник
Хотя мой ответ не имеет отношения к таблицам данных, он решает проблему манипулирования 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
источник
Возможно, я поздно отвечу на этот вопрос. Но все же кому-то мой ответ может принести пользу.
У меня была аналогичная проблема, и в моем случае я не могу изменить директиву, поскольку это библиотека, и изменение кода библиотеки не является хорошей практикой. Поэтому я использовал переменную для ожидания загрузки страницы и использовал 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>
источник
У меня была такая же проблема, но с использованием 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.
источник
Ни одно из решений не помогло мне принять использование тайм-аута. Это потому, что я использовал шаблон, который динамически создавался во время postLink.
Однако обратите внимание, что может быть тайм-аут «0», поскольку тайм-аут добавляет функцию, вызываемую в очередь браузера, которая произойдет после механизма рендеринга angular, поскольку она уже находится в очереди.
Обратитесь к этому: http://blog.brunoscopelliti.com/run-a-directive-after-the-dom-has-finished-rendering
источник
Вот директива, чтобы запрограммировать действия после неглубокой визуализации. Под мелким я подразумеваю, что он будет оцениваться после рендеринга этого самого элемента, и это не будет связано с тем, когда его содержимое будет отрисовано. Поэтому, если вам нужен какой-то подэлемент, выполняющий действие рендеринга публикации, вам следует подумать об использовании его там:
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>
источник
У меня это работает со следующей директивой:
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(); } } });
источник
Поскольку невозможно предвидеть порядок загрузки, можно использовать простое решение.
Давайте посмотрим на отношения директива-«пользователь директивы». Обычно пользователь директивы передает некоторые данные в директиву или использует некоторые функциональные возможности (функции), которые она предоставляет. Директива, с другой стороны, ожидает, что некоторые переменные будут определены в ее области действия.
Если мы сможем убедиться, что все игроки выполнили все свои требования к действиям, прежде чем они попытаются выполнить эти действия, все должно быть хорошо.
А теперь директива:
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. Мы также предполагаем, что компонент, который использует директиву, создается перед директивой, потому что мы зависим от управляющей переменной, которая будет существовать при создании экземпляра директивы.
источник