AngularJS: Як запустити додатковий код після надання AngularJS шаблону?


182

У мене є кутовий шаблон у DOM. Коли мій контролер отримує нові дані від сервісу, він оновлює модель в межах $ і повторно надає шаблон. Все добре поки що.

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

Здається, що має прослухати подію, наприклад, AfterRender, але я не можу знайти нічого такого. Можливо, директива була б способом пройти, але, здається, вона також вистрілила занадто рано.

Ось jsFiddle, яка окреслює мою проблему: Fiddle-AngularIssue

== ОНОВЛЕННЯ ==

Виходячи з корисних коментарів, я відповідно перейшов на директиву для управління маніпуляціями з DOM і застосував модель $ watch всередині директиви. Однак у мене все одно виникає однаковий базовий випуск; код всередині події $ watch запускається до того, як шаблон буде зібраний і вставлений у DOM, тому плагін jquery завжди оцінює порожню таблицю.

Цікаво, що якщо я видаляю виклик асинхронізу, все це справно працює, тож це крок у правильному напрямку.

Ось мій оновлений Fiddle, щоб відобразити ці зміни: http://jsfiddle.net/uNREn/12/


Це схоже на питання, яке у мене було. stackoverflow.com/questions/11444494/… . Можливо, щось там може допомогти.
dnc253

Відповіді:


39

Ця публікація стара, але я змінюю ваш код на:

scope.$watch("assignments", function (value) {//I change here
  var val = value || null;            
  if (val)
    element.dataTable({"bDestroy": true});
  });
}

див. jsfiddle .

Я сподіваюся, що це вам допоможе


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

9
Це застаріло? Коли я це спробую, він фактично дзвонить перед тим, як DOM буде наданий. Це залежить від тіньового дому чи чогось?
Shayne

Я відносно новичок Angular і не можу зрозуміти, як реалізувати цю відповідь у своєму конкретному випадку. Я дивився на різні відповіді та здогадувався, але це не працює. Я не впевнений, якою має бути функція "дивитися". У нашому кутовому додатку є різні англійські директиви, які залежатимуть від сторінки до сторінки, тож як я можу знати, що "дивитися"? Напевно, є простий спосіб звільнити який-небудь код, коли сторінка закінчується, що надається?
moosefetcher

63

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

У мене була така ж проблема і я придумав цей фрагмент. Він використовує $watchта $evalAsyncзабезпечить, щоб ваш код ng-repeatпрацював після таких директив, як розв'язано та шаблони на зразок {{ value }}надані.

app.directive('name', function() {
    return {
        link: function($scope, element, attrs) {
            // Trigger when number of children changes,
            // including by directives like ng-repeat
            var watch = $scope.$watch(function() {
                return element.children().length;
            }, function() {
                // Wait for templates to render
                $scope.$evalAsync(function() {
                    // Finally, directives are evaluated
                    // and templates are renderer here
                    var children = element.children();
                    console.log(children);
                });
            });
        },
    };
});

Сподіваюся, що це може допомогти вам запобігти боротьбі.


Це може бути очевидним, але якщо це не працює для вас, перевірте наявність асинхронних операцій у своїх функціях / контролерах зв'язку. Я не думаю, що $ evalAsync може їх врахувати.
ЛОС

Варто зазначити, що ви також можете комбінувати linkфункцію з a templateUrl.
Wtower

35

Дотримуючись порад Місько, якщо ви хочете асинхронізувати операцію, то замість $ timeout () (що не працює)

$timeout(function () { $scope.assignmentsLoaded(data); }, 1000);

використовувати $ evalAsync () (що працює)

$scope.$evalAsync(function() { $scope.assignmentsLoaded(data); } );

Скрипка . Я також додав посилання "видалити рядок даних", яке змінить $ range.assignments, імітуючи зміну даних / моделі - щоб показати, що зміна даних працює.

У розділі Runtime на сторінці «Концептуальний огляд» пояснюється, що evalAsync слід використовувати, коли вам потрібно щось статися за межами поточного кадру стека, але до того, як браузер відобразиться. (Згадавши тут ... "поточний кадр стека", ймовірно, включає оновлення кутового DOM.) Використовуйте $ timeout, якщо вам потрібно щось статися після відображення браузера.

Однак, як ви вже з’ясували, я не думаю, що тут потрібна робота з асинхронізацією.


4
$scope.$evalAsync($scope.assignmentsLoaded(data));не має жодного сенсу ІМО. Аргументом для $evalAsync()повинна бути функція. У вашому прикладі ви викликаєте функцію в той момент, коли функція повинна бути зареєстрована для подальшого виконання.
hgoebl

@hgoebl, аргументом $ evalAsync може бути функція або вираз. У цьому випадку я використав вираз. Але ти маєш рацію, вираз одразу виконується. Я змінив відповідь, щоб включити її у функцію для подальшого виконання. Дякуємо, що це зробили.
Марк Райчко

26

Я знайшов найпростіше (дешеве і веселе) рішення - просто додати порожній проміжок з ng-show = "someFunctionThatAlwaysReturnsZeroOrNothing ()" до кінця винесеного останнього елемента. Ця функція буде запущена, коли перевірити, чи повинен бути показаний прольотний елемент. Виконайте будь-який інший код у цій функції.

Я усвідомлюю, що це не найвишуканіший спосіб робити речі, однак він працює для мене ...

У мене була схожа ситуація, хоч трохи перевернута, де мені потрібно було зняти індикатор завантаження, коли почалася анімація, на мобільних пристроях кутова ініціалізація набагато швидше, ніж анімація, що відображалася, і використання ng-плащ було недостатньо, оскільки показник завантаження був видалено задовго до відображення реальних даних. У цьому випадку я просто додав функцію повернення 0 до першого виведеного елемента, і в цій функції перевернув var, який приховує показник завантаження. (звичайно, я додав ng-hid до індикатора завантаження, викликаного цією функцією.


3
Це злом напевно, але це саме те, що мені потрібно. Примусив мій код запуститись точно після візуалізації (або дуже близько до точно). В ідеальному світі я би цього не робив, але ось ми ...
Ендрю

Мені потрібен був побудований $anchorScrollсервіс, щоб викликати його після того, як DOM надав, і, здається, нічого не робило, але це. Хакіш, але працює.
Пат Мільяччо

ng-init - краща альтернатива
Flying Gambit

1
ng-init може не завжди працювати. Наприклад, у мене є ng-повтор, який додає до зображення ряд зображень. Якщо я хочу викликати функцію після додавання кожного зображення в ng-повтор, я повинен використовувати ng-show.
Майкл Бремеркамп


4

Нарешті я знайшов рішення, я використовував послугу REST, щоб оновити свою колекцію. Для того, щоб перетворити jquery для даних, слід виконати наступний код:

$scope.$watchCollection( 'conferences', function( old, nuew ) {
        if( old === nuew ) return;
        $( '#dataTablex' ).dataTable().fnDestroy();
        $timeout(function () {
                $( '#dataTablex' ).dataTable();
        });
    });

3

мені доводилося це робити досить часто. У мене є директива, і мені потрібно виконати деякі речі jquery після того, як матеріали повністю завантажені в DOM. тому я поклав свою логіку на посилання: функція директиви і оберніть код у setTimeout (function () {.....}, 1); setTimout запуститься після завантаження DOM, а 1 мілісекунда - найкоротший час після завантаження DOM до того, як код виконається. це, здається, працює для мене, але я хочу, щоб кутовий підняв подію, як тільки шаблон було завантажено, щоб директиви, використовувані цим шаблоном, могли робити речі jquery та отримувати доступ до елементів DOM. сподіваюся, що це допомагає.



2

Ні $ range. $ EvalAsync (), ні $ timeout (fn, 0) не працювали надійно для мене.

Мені довелося поєднувати два. Я зробив директиву, а також поставив пріоритет вище значення за замовчуванням для хорошої міри. Ось директива для цього (Примітка: я використовую ngInject для введення залежностей):

app.directive('postrenderAction', postrenderAction);

/* @ngInject */
function postrenderAction($timeout) {
    // ### Directive Interface
    // Defines base properties for the directive.
    var directive = {
        restrict: 'A',
        priority: 101,
        link: link
    };
    return directive;

    // ### Link Function
    // Provides functionality for the directive during the DOM building/data binding stage.
    function link(scope, element, attrs) {
        $timeout(function() {
            scope.$evalAsync(attrs.postrenderAction);
        }, 0);
    }
}

Щоб викликати директиву, ви зробите це:

<div postrender-action="functionToRun()"></div>

Якщо ви хочете викликати його після запуску ng-повтору, я додав порожній проміжок у свій ng-повтор і ng-if = "$ last":

<li ng-repeat="item in list">
    <!-- Do stuff with list -->
    ...

    <!-- Fire function after the last element is rendered -->
    <span ng-if="$last" postrender-action="$ctrl.postRender()"></span>
</li>

1

У деяких сценаріях, коли ви оновлюєте послугу та переспрямовуєте на новий перегляд (сторінку), а потім ваша директива завантажується до того, як ваші служби оновлюються, ви можете використовувати $ rootScope. $ Трансляція, якщо ваш $ watch або $ timeout виходять з ладу

Вид

<service-history log="log" data-ng-repeat="log in requiedData"></service-history>

Контролер

app.controller("MyController",['$scope','$rootScope', function($scope, $rootScope) {

   $scope.$on('$viewContentLoaded', function () {
       SomeSerive.getHistory().then(function(data) {
           $scope.requiedData = data;
           $rootScope.$broadcast("history-updation");
       });
  });

}]);

Директива

app.directive("serviceHistory", function() {
    return {
        restrict: 'E',
        replace: true,
        scope: {
           log: '='
        },
        link: function($scope, element, attrs) {
            function updateHistory() {
               if(log) {
                   //do something
               }
            }
            $rootScope.$on("history-updation", updateHistory);
        }
   };
});

1

Я прийшов із досить простим рішенням. Я не впевнений, чи це правильно зробити, але це працює в практичному сенсі. Давайте безпосередньо подивимось, що ми хочемо винести. Наприклад, у директиві, яка включає деякі ng-repeats, я б стежив за довжиною тексту (у вас можуть бути інші речі!) Абзаців або всього html. Директива буде такою:

.directive('myDirective', [function () {
    'use strict';
    return {

        link: function (scope, element, attrs) {
            scope.$watch(function(){
               var whole_p_length = 0;
               var ps = element.find('p');
                for (var i=0;i<ps.length;i++){
                    if (ps[i].innerHTML == undefined){
                        continue
                    }
                    whole_p_length+= ps[i].innerHTML.length;
                }
                //it could be this too:  whole_p_length = element[0].innerHTML.length; but my test showed that the above method is a bit faster
                console.log(whole_p_length);
                return whole_p_length;
            }, function (value) {   
                //Code you want to be run after rendering changes
            });
        }
}]);

Зверніть увагу, що код фактично працює після рендеринга змін, а не повного візуалізації. Але я думаю, що в більшості випадків ви можете впоратися з ситуаціями, коли трапляються зміни. Також ви можете подумати про порівняння цієї pдовжини (або будь-якої іншої міри) з вашою моделлю, якщо ви хочете запустити свій код лише один раз після завершення надання. Я вдячний за будь-яку думку з цього приводу.


0

Ви можете використовувати модуль 'jQuery Passthrough' утиліти angular -ui . Я успішно прив’язав плагін-сенсорну карусель jQuery до деяких зображень, які отримую асинхронізацію з веб-сервісу та відтворюю їх з ng-повтором.

Використовуючи наш веб-сайт, ви визнаєте, що прочитали та зрозуміли наші Політику щодо файлів cookie та Політику конфіденційності.
Licensed under cc by-sa 3.0 with attribution required.