Jak mogę uruchomić dyrektywę po zakończeniu renderowania domeny?

115

Mam pozornie prosty problem bez widocznego (czytając dokumentację Angular JS) rozwiązania.

Mam dyrektywę Angular JS, która wykonuje obliczenia na podstawie wysokości innych elementów DOM, aby zdefiniować wysokość kontenera w DOM.

Coś podobnego dzieje się w dyrektywie:

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

Problem polega na tym, że po uruchomieniu dyrektywy $('site-header')nie można jej znaleźć, zwracając pustą tablicę zamiast potrzebnego elementu DOM opakowanego w jQuery.

Czy istnieje wywołanie zwrotne, którego mogę użyć w ramach mojej dyrektywy, które działa dopiero po załadowaniu DOM i mogę uzyskać dostęp do innych elementów DOM za pośrednictwem zwykłych zapytań w stylu selektora jQuery?

Jannis
źródło
1
Możesz użyć scope. $ On () i scope. $ Emit (), aby użyć zdarzeń niestandardowych. Nie jestem jednak pewien, czy jest to właściwe / zalecane podejście.
Tosh

Odpowiedzi:

137

Zależy to od tego, jak skonstruowany jest Twój $ ('site-header').

Możesz spróbować użyć $ timeout z 0 opóźnieniem. Coś jak:

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

Wyjaśnienia, jak to działa: raz , dwa .

Nie zapomnij wstrzyknąć $timeoutw swojej dyrektywie:

.directive('sticky', function($timeout)
Artem Andreev
źródło
5
Dzięki, starałem się, aby to działało przez lata, dopóki nie zdałem sobie sprawy, że nie przeszedłem $timeoutdo dyrektywy. Doh. Teraz wszystko działa, na zdrowie.
Jannis,
5
Tak, musisz przejść $timeoutdo takiej dyrektywy:.directive('sticky', function($timeout) { return function (scope, element, attrs, controller) { $timeout(function(){ }); }); };
Vladimir Starkov
19
Twoje połączone wyjaśnienia wyjaśniają, dlaczego sztuczka limitu czasu działa w JavaScript, ale nie w kontekście AngularJS. Z oficjalnej dokumentacji : „ [...] 4. Kolejka $ evalAsync służy do planowania pracy, która musi być wykonywana poza bieżącą ramką stosu, ale przed renderowaniem widoku przeglądarki. Zwykle jest to wykonywane za pomocą metody setTimeout (0) , ale podejście setTimeout (0) działa wolno i może powodować migotanie widoku, ponieważ przeglądarka renderuje widok po każdym zdarzeniu. [...] "(moje wyróżnienie)
Alberto,
12
Mam podobny problem i stwierdziłem, że potrzebuję około 300 ms, aby umożliwić załadowanie DOM przed wykonaniem mojej dyrektywy. Naprawdę nie lubię wpisywać pozornie dowolnych liczb w ten sposób. Jestem pewien, że prędkości ładowania DOM będą się różnić w zależności od użytkownika. Jak więc mogę mieć pewność, że 300 ms będzie działać dla każdego, kto używa mojej aplikacji?
keepitreal
5
niezbyt zadowolony z tej odpowiedzi ... chociaż wydaje się, że odpowiada na pytanie OP ... jest bardzo specyficzny dla ich przypadku i ma związek z bardziej ogólną postacią problemu (np. uruchomienie dyrektywy po załadowaniu domeny) nie jest oczywiste + to jest po prostu zbyt hacky ... nic w tym szczególnego o kanciastym w ogóle
opactwo
44

Oto jak to robię:

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

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

});
rjm226
źródło
1
Nie powinien nawet potrzebować elementu kątowego, ponieważ element jest już tam dostępny:element.ready(function(){
timhc22
1
Element @ timhc22 jest odwołaniem do DOMElement, który wywołał dyrektywę. Twoje zalecenie nie spowoduje odwołania DOMElement do obiektu Document Pages.
tobius,
to nie działa poprawnie. Otrzymuję offsetWidth = 0 dzięki temu podejściu
Alexey Sh.
37

Prawdopodobnie autor nie będzie już potrzebował mojej odpowiedzi. Jednak ze względu na kompletność uważam, że inni użytkownicy mogą uznać to za przydatne. Najlepszym i najprostszym rozwiązaniem jest użycie $(window).load()wewnątrz treści zwracanej funkcji. (alternatywnie możesz użyć document.ready. To naprawdę zależy, czy potrzebujesz wszystkich obrazów, czy nie).

$timeoutMoim skromnym zdaniem używanie jest bardzo słabą opcją i w niektórych przypadkach może się nie powieść.

Oto pełny kod, którego użyłbym:

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

           $(window).load(function() {
               //...JS here...
           });
       }
   }
});
jnardiello
źródło
1
Czy możesz wyjaśnić, dlaczego to „może się nie udać w niektórych przypadkach”? Jakie sprawy masz na myśli?
rryter
6
Zakładasz, że jQuery jest dostępne tutaj.
Jonathan Cremin
3
@JonathanCremin Wybór jQuery to omawiany problem zgodnie z OP
Nick Devereaux
1
Działa to świetnie, jednak jeśli istnieje post, który buduje nowe elementy za pomocą dyrektywy, ładowanie okna nie zostanie uruchomione po początkowym załadowaniu i dlatego nie będzie działać poprawnie.
Brian Scott,
@BrianScott - Użyłem kombinacji $ (window) .load do początkowego renderowania strony (mój przypadek użycia czekał na osadzone pliki czcionek), a następnie element.ready, aby zająć się przełączaniem widoków.
aaaaaa
8

jest ngcontentloadedwydarzenie, myślę, że możesz to wykorzystać

.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
źródło
21
Czy możesz wyjaśnić, co $ $windowto robi?
Sum
2
wygląda jak skrypt coffeescript, może miał to być $ ($ window) i $ window wstrzyknięte do dyrektywy
mdob
5

Jeśli nie możesz użyć $ timeout ze względu na zasoby zewnętrzne i nie możesz użyć dyrektywy z powodu konkretnego problemu z synchronizacją, użyj emisji.

Dodaj $scope.$broadcast("variable_name_here"); po zakończeniu żądanego zasobu zewnętrznego lub długotrwałego kontrolera / dyrektywy.

Następnie dodaj poniższe po załadowaniu zasobu zewnętrznego.

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

Na przykład w obietnicy odroczonego żądania HTTP.

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

$scope.$on("imageLoaded", function(){ 
   jQuery('img').height(80).width(80); 
}
JSV
źródło
2
To nie rozwiąże problemu, ponieważ załadowane dane nie oznaczają, że są już renderowane w DOM, nawet jeśli znajdują się w odpowiednich zmiennych zasięgu przypisanych do elementów DOM. Istnieje okres czasu między momentem załadowania ich do zakresu a wyrenderowanymi danymi wyjściowymi w dom.
René Stalder
1

Miałem podobny problem i chcę podzielić się tutaj moim rozwiązaniem.

Mam następujący HTML:

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

Problem: W funkcji linkującej dyrektywy nadrzędnego div chciałem jzapytać o podrzędny div # sub. Ale to po prostu dało mi pusty obiekt, ponieważ ng-include nie skończył się, gdy uruchomiono funkcję link dyrektywy. Więc najpierw zrobiłem brudne obejście z $ timeout, który działał, ale parametr opóźnienia zależał od szybkości klienta (nikt tego nie lubi).

Działa, ale jest brudny:

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;
}]);

Oto czyste rozwiązanie:

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;
}]);

Może to komuś pomoże.

jaheraho
źródło