Как я могу запустить директиву после завершения рендеринга dom?

115

У меня, казалось бы, простая проблема без очевидного (читая документацию Angular JS) решения.

У меня есть директива Angular JS, которая выполняет некоторые вычисления на основе высоты других элементов DOM для определения высоты контейнера в DOM.

Что-то похожее на это происходит внутри директивы:

return function(scope, element, attrs) {
    $('.main').height( $('.site-header').height() -  $('.site-footer').height() );
}

Проблема в том, что при запуске директивы $('site-header')невозможно найти, возвращая пустой массив вместо элемента DOM, обернутого jQuery, который мне нужен.

Есть ли обратный вызов, который я могу использовать в своей директиве, который запускается только после загрузки DOM, и я могу получить доступ к другим элементам DOM через обычные запросы стиля селектора jQuery?

Jannis
источник
1
Вы можете использовать scope. $ On () и scope. $ Emit () для использования настраиваемых событий. Не уверен, что это правильный / рекомендуемый подход.
Tosh

Ответы:

137

Это зависит от того, как построен ваш $ ('site-header').

Вы можете попробовать использовать $ timeout с нулевой задержкой. Что-то вроде:

return function(scope, element, attrs) {
    $timeout(function(){
        $('.main').height( $('.site-header').height() -  $('.site-footer').height() );
    });        
}

Пояснения, как это работает: раз , два .

Не забудьте ввести $timeoutв свою директиву:

.directive('sticky', function($timeout)
Артем Андреев
источник
5
Спасибо, я пытался заставить это работать целую вечность, пока не понял, что не попал $timeoutв директиву. Doh. Теперь все работает, ура.
Jannis
5
Да, вам нужно перейти $timeoutк такой директиве:.directive('sticky', function($timeout) { return function (scope, element, attrs, controller) { $timeout(function(){ }); }); };
Владимир Старков
19
Ваши связанные объяснения объясняют, почему трюк с тайм-аутом работает в JavaScript, но не в контексте AngularJS. Из официальной документации : " [...] 4. Очередь $ evalAsync используется для планирования работы, которая должна выполняться за пределами текущего кадра стека, но до визуализации представления браузера. Обычно это делается с помощью setTimeout (0) , но подход setTimeout (0) страдает медленностью и может вызвать мерцание изображения, поскольку браузер отображает представление после каждого события. [...] "(выделено мной)
Альберто
12
Я столкнулся с аналогичной проблемой и обнаружил, что мне нужно около 300 мс, чтобы позволить DOM загрузиться перед выполнением моей директивы. Я действительно не люблю вставлять такие, казалось бы, произвольные числа. Я уверен, что скорость загрузки DOM будет зависеть от пользователя. Итак, как я могу быть уверен, что 300 мс сработают для всех, кто использует мое приложение?
keepitreal
5
не слишком доволен этим ответом ... хотя он, кажется, отвечает на вопрос OP ... он очень специфичен для их случая и имеет отношение к более общей форме проблемы (т.е. запуск директивы после загрузки dom) не очевиден + это просто слишком хакерское ... в нем вообще ничего конкретного об angular
abbood
44

Вот как я это делаю:

app.directive('example', function() {

    return function(scope, element, attrs) {
        angular.element(document).ready(function() {
                //MANIPULATE THE DOM
        });
    };

});
rjm226
источник
1
Angular.element даже не нужен, потому что элемент там уже доступен:element.ready(function(){
timhc22 01
1
Элемент @ timhc22 - это ссылка на DOMElement, который запустил директиву, ваша рекомендация не приведет к ссылке DOMElement на объект страниц Document.
tobius
это не работает должным образом. Благодаря такому подходу я получаю offsetWidth = 0
Алексей Ш.
37

Наверное, автору мой ответ больше не понадобится. Тем не менее, для полноты картины я считаю, что другие пользователи могут счесть это полезным. Лучшее и самое простое решение - использовать $(window).load()внутри тела возвращаемой функции. (В качестве альтернативы вы можете использовать document.ready. Это действительно зависит от того, нужны вам все изображения или нет).

По $timeoutмоему скромному мнению, использование - очень слабый вариант и в некоторых случаях может потерпеть неудачу.

Вот полный код, который я бы использовал:

.directive('directiveExample', function(){
   return {
       restrict: 'A',
       link: function($scope, $elem, attrs){

           $(window).load(function() {
               //...JS here...
           });
       }
   }
});
jnardiello
источник
1
Не могли бы вы объяснить, почему он «в некоторых случаях может дать сбой»? Какие случаи вы имеете в виду?
rryter
6
Вы предполагаете, что здесь доступен jQuery.
Джонатан Кремин
3
@JonathanCremin Выбор jQuery - это актуальная проблема в соответствии с OP
Ник Деверо,
1
Это отлично работает, однако, если есть сообщение, которое создает новые элементы с помощью директивы, тогда загрузка окна не будет запускаться после начальной загрузки и, следовательно, не будет работать правильно.
Брайан Скотт,
@BrianScott - я использовал комбинацию $ (window) .load для начального рендеринга страницы (мой вариант использования ожидал файлы встроенных шрифтов), а затем element.ready, чтобы позаботиться о переключении представлений.
аааааа
8

есть ngcontentloadedмероприятие, думаю, вы можете его использовать

.directive('directiveExample', function(){
   return {
       restrict: 'A',
       link: function(scope, elem, attrs){

                $$window = $ $window


                init = function(){
                    contentHeight = elem.outerHeight()
                    //do the things
                }

                $$window.on('ngcontentloaded',init)

       }
   }
});
sunderls
источник
21
Вы можете объяснить, что $ $windowделает?
Catfish
2
похоже на какой-то coffeescript, может быть, это должно было быть $ ($ window) и $ window, введенное в директиву
mdob
5

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

Добавить $scope.$broadcast("variable_name_here");после завершения желаемого внешнего ресурса или длительного контроллера / директивы.

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

$scope.$on("variable_name_here", function(){ 
   // DOM manipulation here
   jQuery('selector').height(); 
}

Например, в обещании отложенного HTTP-запроса.

MyHttpService.then(function(data){
   $scope.MyHttpReturnedImage = data.image;
   $scope.$broadcast("imageLoaded");
});

$scope.$on("imageLoaded", function(){ 
   jQuery('img').height(80).width(80); 
}
JSV
источник
2
Это не решит проблему, поскольку загруженные данные не означают, что они уже отображаются в DOM, даже если они находятся в соответствующих переменных области видимости, привязанных к элементам DOM. Между моментом их загрузки в область видимости и визуализацией вывода в dom существует промежуток времени.
René Stalder
1

У меня была аналогичная проблема, и я хочу поделиться своим решением здесь.

У меня есть следующий HTML:

<div data-my-directive>
  <div id='sub' ng-include='includedFile.htm'></div>
</div>

Проблема: в функции ссылки директивы родительского div я хотел jquery'ing дочернего div # sub. Но он просто дал мне пустой объект, потому что ng-include не завершил работу, когда выполнялась функция ссылки директивы. Итак, сначала я сделал грязный обходной путь с помощью $ timeout, который работал, но параметр задержки зависел от скорости клиента (никому это не нравится).

Работает, но грязно:

app.directive('myDirective', [function () {
    var directive = {};
    directive.link = function (scope, element, attrs) {
        $timeout(function() {
            //very dirty cause of client-depending varying delay time 
            $('#sub').css(/*whatever*/);
        }, 350);
    };
    return directive;
}]);

Вот чистое решение:

app.directive('myDirective', [function () {
    var directive = {};
    directive.link = function (scope, element, attrs) {
        scope.$on('$includeContentLoaded', function() {
            //just happens in the moment when ng-included finished
            $('#sub').css(/*whatever*/);
        };
    };
    return directive;
}]);

Может, это кому-то поможет.

jaheraho
источник