How can I run a directive after the dom has finished rendering?

131,564

Solution 1

It depends on how your $('site-header') is constructed.

You can try to use $timeout with 0 delay. Something like:

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

Explanations how it works: one, two.

Don't forget to inject $timeout in your directive:

.directive('sticky', function($timeout)

Solution 2

Here is how I do it:

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

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

});

Solution 3

Probably the author won't need my answer anymore. Still, for sake of completeness i feel other users might find it useful. The best and most simple solution is to use $(window).load() inside the body of the returned function. (alternatively you can use document.ready. It really depends if you need all the images or not).

Using $timeout in my humble opinion is a very weak option and may fail in some cases.

Here is the complete code i'd use:

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

           $(window).load(function() {
               //...JS here...
           });
       }
   }
});

Solution 4

there is a ngcontentloaded event, I think you can use it

.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)

       }
   }
});

Solution 5

If you can't use $timeout due to external resources and cant use a directive due to a specific issue with timing, use broadcast.

Add $scope.$broadcast("variable_name_here"); after the desired external resource or long running controller/directive has completed.

Then add the below after your external resource has loaded.

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

For example in the promise of a deferred HTTP request.

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

$scope.$on("imageLoaded", function(){ 
   jQuery('img').height(80).width(80); 
}
Share:
131,564

Related videos on Youtube

Jannis
Author by

Jannis

Updated on July 12, 2022

Comments

  • Jannis
    Jannis almost 2 years

    I've got a seemingly simple problem with no apparent (by reading the Angular JS docs) solution.

    I have got an Angular JS directive that does some calculations based on other DOM elements' height to define the height of a container in the DOM.

    Something similar to this is going on inside the directive:

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

    The issue is that when the directive runs, $('site-header') cannot be found, returning an empty array instead of the jQuery wrapped DOM element I need.

    Is there a callback that I can use within my directive that only runs after the DOM has been loaded and I can access other DOM elements via the normal jQuery selector style queries?

    • Tosh
      Tosh over 11 years
      You could use scope.$on() and scope.$emit() to use custom events. Not sure whether this is the right / recommended approach though.
  • Jannis
    Jannis over 11 years
    Thanks, I tried getting this to work for ages until I realised I hadn't passed $timeout into the directive. Doh. Everything works now, cheers.
  • Vladimir Starkov
    Vladimir Starkov over 11 years
    Yes, you need to pass $timeout to directive like this: .directive('sticky', function($timeout) { return function (scope, element, attrs, controller) { $timeout(function(){ }); }); };
  • Pencilcheck
    Pencilcheck almost 11 years
    I need to increase the timeout to work, something around 500ms
  • Alberto
    Alberto almost 11 years
    Your linked explanations explain why the timeout trick works in JavaScript, but not in the context of AngularJS. From the official documentation: "[...] 4. The $evalAsync queue is used to schedule work which needs to occur outside of current stack frame, but before the browser's view render. This is usually done with setTimeout(0), but the setTimeout(0) approach suffers from slowness and may cause view flickering since the browser renders the view after each event.[...]" (emphasis mine)
  • keepitreal
    keepitreal about 10 years
    I'm facing a similar problem and have found that I need around 300ms to allow the DOM to load before executing my directive. I really don't like plugging in seemingly arbitrary numbers like that. I'm sure DOM loading speeds will vary depending on the user. So how can I be sure 300ms will work for anyone using my app?
  • rryter
    rryter about 10 years
    Can you elaborate why it "may fail in some cases"? What cases do you mean?
  • Jonathan Cremin
    Jonathan Cremin almost 10 years
    You're assuming jQuery is available here.
  • abbood
    abbood almost 10 years
    not too happy about this answer.. while it seems to answer the OP's question.. it's very specific to their case and it's relevance to the more general form of the problem (ie running a directive after a dom has loaded) isn't obvious + it's just too hacky.. nothing in it specific about angular at all
  • Nick Devereaux
    Nick Devereaux almost 10 years
    @JonathanCremin jQuery selecting is the issue at hand as per the OP
  • Adam
    Adam almost 10 years
    This works for me: See: lorenzmerdian.blogspot.de/2013/03/… I have to set timeout to 400ms.
  • Catfish
    Catfish over 9 years
    Can you explain what the $ $window is doing?
  • René Stalder
    René Stalder over 9 years
    This is not going to solve the problem, since loaded data does not mean, that they are already rendered in the DOM, even if they are in the proper scope variables bound to DOM elements. There's a timespan between the moment they are loaded in the scope and the rendered output in the dom.
  • z0r
    z0r over 9 years
    If I understand correctly, $timeout(..., 0) will try to run your code before the page is rendered. If you find you need a non-zero delay then you might need the page to be rendered, so try $window.setTimeout(..., 0) instead. ... I made a demo in Plunker, but I can't reproduce the problem. Can anyone help?
  • Brian Scott
    Brian Scott over 9 years
    This works great however if there's a post that builds new items with the directive then the window load will not fire after the initial load and therefore will not function correctly.
  • aaaaaa
    aaaaaa about 9 years
    @BrianScott - I used a combination of $(window).load for initial page rendering (my use-case was awaiting embedded font files) and then element.ready to take care of switching views.
  • surrender_to_the_Now
    surrender_to_the_Now about 9 years
    @ArtemAndreev It works better than all the other solutions. There is no delay. Thanks!
  • mdob
    mdob almost 9 years
    looks like some coffeescript, maybe it was meant to be $($window) and $window being injected into directive
  • timhc22
    timhc22 over 7 years
    Shouldn't even need angular.element because element is already available there: element.ready(function(){
  • tobius
    tobius over 7 years
    @timhc22 element is a reference to the DOMElement that triggered the directive, your recommendation would not result in a DOMElement reference to the pages Document object.
  • Zia Ul Rehman Mughal
    Zia Ul Rehman Mughal about 7 years
    What if i need to run some initialization code for the directive(say some function which returns configurations etc which depend on page load) closely related problem. If covered in same answer would be great
  • Alexey Sh.
    Alexey Sh. almost 5 years
    that doesn't work properly. I'm getting offsetWidth = 0 through this approach