чи є зворотний виклик після візуалізації для кутової директиви 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 .

Але проблема полягає в тому, що я повинен викликати таблицю даних () у таблиці # 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>"

    }

І я дійсно бачу "бу" у div # sayboo.

Потім я спробую свій дзвінок з даних, що передається jquery

.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 стовпцями та 100 рядками, так що, природно, кутовий виглядає як хороший варіант використання для прив'язки даних; але мені також потрібно використовувати плагін jquery Datatables, який простий як $ ('table'). datatable (). Використовуючи директиву або просто маю тупий об’єкт json з усіма рядками та використовуючи ng-повтор для ітерації, я не можу отримати свій $ (). Datatable () для запуску ПІСЛЯ подання html-елемента таблиці, тому в даний час моя хитрість полягає в таймері щоб перевірити, чи $ ('tr'). довжина> 3 (b / c колонтитула / колонтитулу)
Nik So

2
@adardesign І так, я спробував весь метод компіляції, метод компіляції, що повертає об'єкт, що містить методи postLink / preLink, метод компіляції, що повертає лише функцію (а саме функцію зв’язування), метод зв’язування (без методу компіляції, тому що, наскільки я можу сказати, якщо у вас є метод компіляції, який повертає метод зв’язування, функція зв’язування ігнорується). Жоден не працював, тому не слід покладатися на старий добрий $ timeout. Буде оновлено цю публікацію, якщо я знайду щось, що працює краще або просто, коли я виявлю, що зворотний виклик дійсно діє як зворотний дзвінок
Nik So

Відповіді:


215

Якщо другий параметр "затримка" не передбачений, поведінка за замовчуванням полягає у виконанні функції після завершення надання DOM. Тому замість setTimeout використовуйте $ timeout:

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

8
Чому це не пояснено в документах ?
Гауї

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

7
Розглядайте браузер як чергування певних завдань, таких як "виконання javascript" та "DOM-рендерінг" окремо, а те, що setTimeout (fn, 0) підштовхує поточне виконання "виконання JavaScript" на задній частині черги після візуалізації .
парламент

2
@GabLeRoux yup, що матиме такий самий ефект, окрім $ timeout має додаткову перевагу виклику $ range. $ Apply () після його запуску. З _.defer () вам потрібно буде викликати його вручну, якщо myFunction змінює змінні в області.
парламент

2
У мене є сценарій, коли це не допомагає, коли на page1 ng-повторення відображається купа елементів, потім я переходжу на сторінку2, а потім повертаюся до сторінки1 і намагаюся отримати високий рівень ng-повторних елементів батьків ... вона повертає неправильну висоту. Якщо я роблю тайм-аут приблизно 1000 мс, тоді він працює.
yodalr

14

У мене була така ж проблема, і я вважаю, що відповідь насправді - ні. Дивіться коментар Мішко та деяку дискусію в групі .

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

Ми вирішили це евристично за допомогою setTimeout, як і ви.

(Будь ласка, майте на увазі, що не всі погоджуються зі мною - ви повинні прочитати коментарі за посиланнями вище і подивитися, що ви думаєте.)


7

Ви можете використовувати функцію "link", також відому як 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 дня я провів за читанням на цій сторінці директив; а також переглядаючи приклади на кутовому сайті. Спробуй ваш код зараз.
Nik So

Ах, я бачу, ти зараз намагався зробити посилання, але ти робив це неправильно. Якщо ви просто повернете функцію, передбачається, що це посилання. Якщо ви повертаєте об'єкт, ви повинні повернути його ключем як "посилання". Ви також можете повернути функцію сполучення з функції компіляції.
Ендрю Джослін

Привіт Енді, повернув мої результати; Я майже втратив розум, тому що я дійсно в основному робив те, що тут є ваша відповідь. Будь ласка, дивіться моє оновлення
Nik So

Гум, спробуйте щось на зразок: <table id = "bob"> <tbody dashboard-table = "# bob"> </tbody> </table> Потім у своєму посиланні зробіть $ (attrs.dashboardTable) .dataTable () для переконайтесь, що він обраний правильно. Або я думаю, ви вже пробували це. Я дійсно не впевнений, що посилання не працює.
Ендрю Джослін

Цей працював для мене, я хотів перемістити елементи в домашньому розміщенні шаблону за моєю вимогою, зробив це у функції посилання.
Дякую

7

Хоча моя відповідь не стосується таблиць даних, вона вирішує питання маніпуляції з DOM, наприклад, ініціалізація плагінів jQuery для директив, що використовуються для елементів, які оновлюють їх вміст асинхронним способом.

Замість впровадження тайм-ауту можна просто додати годинник, який прослухає зміни вмісту (або навіть додаткові зовнішні тригери).

У моєму випадку я використав це рішення для ініціалізації плагіна jQuery, як тільки було виконано ng-повтор, який створив мій внутрішній DOM - в іншому випадку я використовував його для простого маніпулювання DOM після зміни властивості області на контролері. Ось як я зробив ...

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-direktive-watch, можна було б уявити будь-яке довільне вираження.

Примітка: Ізоляція області, як у наведеному вище прикладі, може бути виконана лише один раз на кожен елемент - спроба зробити це за допомогою декількох директив одного і того ж елемента призведе до компіляції $: помилка multidir - див .: https://docs.angularjs.org / помилка / $ 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+ групуванням рядків + $, складеними вкладеними директивами. Я помістив $ timeout у своїй fnDrawCallbackфункції, щоб виправити візуалізацію сторінки.

Перед прикладом на основі джерела_груповання рядків:

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 () задушитись
Nik So

Я виявив, що ця проблема сталася під час навігації сторінок DataTable для наборів результатів, що складаються з 2 рядків до понад 150 рядків Отже, ні - я не думаю, що проблема стала величиною таблиці, але, можливо, DataTable додав достатньо рендерингу, щоб пережовувати частину цих мілісекунд. Моя увага зосереджена на тому, щоб згрупувати рядки для роботи в DataTable з мінімальною інтеграцією AngularJS.
JJ Zabkar

2

Жодне з розроблених для мене рішень не приймає використання тайм-ауту. Це тому, що я використовував шаблон, який динамічно створювався під час postLink.

Зауважте, що може виникнути час очікування "0", оскільки тайм-аут додає функцію, яка викликається до черги браузера, яка відбудеться після двигуна кутового візуалізації, оскільки це вже в черзі.

Зверніться до цього: 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" є еквівалентом "налаштування даних". Кутовий змінює формат у корпус верблюда.

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.