angular / angular.js

AngularJS - HTML enhanced for web apps!
https://angularjs.org
MIT License
58.84k stars 27.52k forks source link

Feature request: a way to detect if Angular finished rendering of HTML DOM #734

Closed kstep closed 12 years ago

kstep commented 12 years ago

We often need to do some operations on DOM nodes, e.g. bind custom events to them (like "mouseover", "click" or "dblclick"), but Angular changes DOM eventually at the end of digest cycle.

What we need is a way to bind some handler upon DOM modification by Angular is finished and it's safe to handle DOM elements (so they won't disappear next moment). From what I understand ng:init runs only once per node on first document rendering and doesn't guarantee any surrounding nodes are rendered.

Is there a way to set a hook to run on end of digest cycle after all DOM nodes are rendered, so it's safe to go on with DOM operations?

IgorMinar commented 12 years ago

Why isn't creating directives sufficient to bind these events? The thing is that the compilation is a continuous process in angular, if you don't use directives, you are bypassing the compiler and then you run the issue of not knowing when things are ready.

If I'm missing something (which I may be!), can you create a simple jsfiddle demonstrating your use case?

mhevery commented 12 years ago

I think you are looking for this: http://docs-next.angularjs.org/api/angular.module.ng.$rootScope.Scope#$evalAsync

2012/1/25 Konstantin Stepanov < reply@reply.github.com

We often need to do some operations on DOM nodes, e.g. bind custom events to them (like "mouseover", "click" or "dblclick"), but Angular changes DOM eventually at the end of digest cycle.

What we need is a way to bind some handler upon DOM modification by Angular is finished and it's safe to handle DOM elements (so they won't disappear next moment). From what I understand ng:init runs only once per node on first document rendering and doesn't guarantee any surrounding nodes are rendered.

Is there a way to set a hook to run on end of digest cycle after all DOM nodes are rendered, so it's safe to go on with DOM operations?


Reply to this email directly or view it on GitHub: https://github.com/angular/angular.js/issues/734

kstep commented 12 years ago

$evalAsync looks interesting, but not exactly what we want. According to docs it evaluates expression before any DOM rendering is done and after a digest cycle is finished. What we want is to run some code after both digest cycle is finished and DOM rendering is done.

Either I or my colleague will provide you an example tomorrow, I'm too tired for today and my colleague is not available at the moment either.

kstep commented 12 years ago

Quick example: we load some partial with <ng:view/>, then this partial is rendered (e.g. a lot of ng:repeat's and other ng:bind stuff is evaluated), and as the result of this (potentially long) process DOM becomes stable (I guess after the end of digest cycle), so it's safe to apply some special effects to it, like drag-n-drop handling, fading in/out, custom event handlers binding, applying some jQuery plugins effects like Masonry, LazyLoad (to load images in a lazy fashion), or run Facebook handlers (it's a real problem for us, as Facebook XFBML parser requires already existing fb:* DOM nodes, which is difficult to guarantee with asynchronous DOM rendering).

For now we use $browser.defer() here and there, but it's a very bad thing, we'd prefer more robust and correct way to do it.

mhevery commented 12 years ago

The asyncEval is after the DOM construction but before the browser renders. I believe that is the time you want to attach the jquery plugins. otherwise you will have flicker. if you really want to do after the browser render you can do $defer(fn, 0);

2012/1/25 Konstantin Stepanov < reply@reply.github.com

Quick example: we load some partial with ng:view/, then this partial is rendered (e.g. a lot of ng:repeat's and other ng:bind stuff is evaluated), and as the result of these (potentially long) process DOM becomes stable (I guess after the end of digest cycle), so it's same to apply some special effects to it, like drag-n-drop handling, fading in/out, custom event handlers binding, applying some jQuery plugins effects like Masonry, LazyLoad (to load images in a lazy fashion), or run Facebook handlers (it's a real problem for us, as Facebook XFBML parser requires already existing fb:* DOM nodes, which is difficult to guarantee with asynchronous DOM rendering).

For now we use $browser.defer() here and there, but it's a very bad thing, we'd prefer more robust and correct way to do it.


Reply to this email directly or view it on GitHub: https://github.com/angular/angular.js/issues/734#issuecomment-3656751

kstep commented 12 years ago

Hmm... I looked through code and what I found out, $evalAsync just queues code for execution, and then it's executed in $digest, but there in $digest first execution queue (filled with $evalAsync) is run, and then watchers are evaluated. And all widgets/directives use $watch to update DOM nodes. So if I get it correctly, my $evalAsync()ed code run before DOM manipulations, and thus I have no guarantee my DOM tree won't be modified just after my $evalAsync()ed function executed.

Am I missing something?

I feel like I need something like additional execution queue to run after the main $digest loop is done it's work. Of cause I will have to guarantee my code in the queue won't change DOM lest $digest must be run again.

mhevery commented 12 years ago

if you enqueue from controller then it will be before, but if you enqueue from directive then it will be after.

The issue is that most frameworks have a string template which then gets rendered into a string which then gets innerHTML. In such a world it is very clear where the beggining and end of render is.

With angular that is not the case. Since angular has concept of directives, a directive may unroll a loop, which can the instantiate a controller, which can than modify the model which then causes more DOM updates. This makes it hard to have clear line in a send as to where the DOM update begins and ends.

So in order for us to help you, what is the specific problem which you are trying to solve? If you just want to apply special animations to do, then those animations need to be triggered from within directives, in which case the $asyncEval is exactly what you want as the current DOM update has become stable.

-- Misko

2012/1/26 Konstantin Stepanov < reply@reply.github.com

Hmm... I looked through code and what I found out, $evalAsync just queues code for execution, and then it's executed in $digest, but there in $digest first execution queue (filled with $evalAsync) is run, and then watchers are evaluated. And all widgets/directives use $watch to update DOM nodes. So if I get it correctly, my $evalAsync()ed code run before DOM manipulations, and thus I have no guarantee my DOM tree won't be modified just after my $evalAsync()ed function executed.


Reply to this email directly or view it on GitHub: https://github.com/angular/angular.js/issues/734#issuecomment-3669667

IgorMinar commented 12 years ago

we now have $viewContentLoaded and $includeContentLoaded events that are emitted in ng-view and ng-include respectively. I think this is as close as one can get to knowing when we are done with the compilation.

eprouver commented 12 years ago

I think I have the same problem. When using a directive that contains another directive (ng-repeat) the higher level doesn't seem to wait until the entire DOM is created, and the async eval is evaluated before all of the DOM is present.

Could you suggest a good time to bind jQuery events? In the example I'm using jQuery UI draggable.

http://jsfiddle.net/eprouver/WpZZE/4/

paramburu commented 12 years ago

I'm also having problems to detect when the ngView has ended rendering. The task need to get the ngView "container"s height but when the event $viewContentLoaded triggers, height is still 0. What should I do? I'll appreciate a little help.

kstep commented 12 years ago

Have you tried using $timeout() to schedule your code execution in $viewContentLoaded event handler? Or evalAsync()?

kstep commented 12 years ago

@eprouver try this out http://jsfiddle.net/WpZZE/5/

eprouver commented 12 years ago

Thanks for responding: With local data $timeout seemed to work. I believe I started running into this problem when loading outside content. Here is a simple example that fails similarly: http://jsfiddle.net/eprouver/WpZZE/6/

kstep commented 12 years ago

@eprouver How about creating my-draggable directive instead? http://jsfiddle.net/gXSLb/1/ I consider this is most clean and angularish way of doing what you want.

eprouver commented 12 years ago

Ah, yes, perfect... this is exactly the mindset that I was missing. Thanks!

abdulazeezsk commented 11 years ago

Hi,

I need similar functionality in my code. directive template's expressions are not evaluated by the time listener in watch gets called. Here is the code,

http://stackoverflow.com/questions/12748392/expressions-are-not-evaluated-in-watch-of-custom-directive-of-angularjs

Didn't get any reply on google group and stack overflow?

Can anyone help me with this?

Thanks, Abdul

todiadiyatmo commented 11 years ago

@kstep @eprouver

Hi, I'am quite new to angular. The solution for this problem for me is to detect the dom element change on the parrent element where the ng-repeat attribute apply.

So for @eprouver fiddle, i attach a listener to the

kstep commented 11 years ago

Your code is nice and works fine, but it doesn't involve AngularJS (except for controller), so it doesn't know about a thing about AngularJS DOM rebuilding and scopes life-cycle. It works by detecting side-effect event of AngularJS DOM rendering (you detect resize event of ul container) instead of direct information from AngularJS.

That is, the problem is you code can stop working if AngularJS rendering produces DOM with sizes identical to previous iteration (e.g. the same number of pictures are loaded, so DOM elements can change w/o changing container's size → your code won't put draggable thing on these new elements). Just try to click "Add" button in your fiddle second time and you will see what I mean.

Besides your code lacks access to AngularJS's scope, rendering it unable to take/pass data from/to controllers (which is not used in your fiddle anyway, but is a drawback of your approach nevertheless).

erkulas commented 11 years ago

This is a side-effect of the all async nature of the AngularJS setup. There are other side-effects as well. For me total async behaviour (all elements on page doing their own thing) is not the best solution - there has to be some balance.

Anyway. We solved this by placing a 0-size transparent gif in the template and putting a onload event that runs when the image is rendered in DOM. Made a directive out of this. I could make a JSFiddle if anybody is interested.

This solution also has it's drawbacks due to the way AngularJS operates. For example we put the image in the template that had a ng-repeat loop and we wanted to access the looped elements after they are in DOM. It only started working when we put the image actually inside the loop element(s) because DOM is the Angular templating system and the other parts ended up in DOM before the looped elements did. So. this method works but with quirks that you have to account for.

mokesmokes commented 11 years ago

Seems that in most cases here people want to trigger jQuery plugins - usually angular-ui with the jQuery passthrough directive does the job. Using it successfully for Lazyload with Ajax-loaded images. Note: I did have a small issue with this but not because of Angular - but rather because Lazyload is not designed for dynamically added images.

wuxiaoying commented 11 years ago

Here's a use case: I have a directive that draws a canvas on top of div elements. The canvas needs to know the div positions in order to draw the right diagram. This means that it has to update when the div finishes rendering (the div's size depends on text that the directive also sets) so that it knows its top, left, height, and width properties. I've used the timeout hack to get it to render after but its still not reliable.

eprouver commented 11 years ago

@wuxiaoying - I've used $attrs.$observe for a similar case and put the size-defining text in a data-text attribute.

It worked for me... here is an example: http://jsfiddle.net/eprouver/Xnacm/

wuxiaoying commented 11 years ago

@eprouver hmm that's an interesting solution; thanks for sharing!

However, isn't that also a hack? it's basically setting an attribute to an interpolated value, observing the interpolation, and assuming that since that value was interpolated, the value inside the div is also interpolated.

eprouver commented 11 years ago

@wuxiaoying - Yeah... it's a hack. I think, generally, it's a good idea to make a specific directive for your divs that generate canvas charts. Similar to creating directives for jQuery ui widgets. That $attrs hack just proved more reliable for me than setTimeout.

wuxiaoying commented 11 years ago

My directive is specific :) it basically draws diagrams around divs, so the canvas drawing itself is related to div positioning. I would use the $attr hack, but I'm not sure it work in my case since the canvas positioning doesn't only depend on text, but also child divs and and other elements. I guess the timeout hack will work for now; I realized that the few unreliable cases were when the page went invisible (due to a tab switch) before the timeouts occurred and swwtching back to that tab resulted in a very messed up looking canvas :).

LukeStone commented 11 years ago

There's no event for "finished rendering HTML DOM" because things may be mutating the DOM at any time.

IMHO the best way to handle this is to come up with a heuristic for whether the DOM has changed in a way that's interesting to you, set up a watch on this, then trigger whatever behavior you need.

onethread commented 10 years ago

I ran into a similar problem and found a solution similar to eprouver's. I have a popup-style directive (very similar to angular-ui's) but it supports ajax-driven data and templates. The popup is properly positioned relative to its parent based on its size, which, based on the data and template, can vary.

Unfortunately, I haven't seen a way to get a notification from angular that the popup's HTML has been updated, so I could never properly position the popup relative to the parent after data load.

Anyway, I noticed that angular's $animate service has functions with a callback that meets my needs. I'm guessing it's doing something similar to eprouver's solution but behind the scenes. I used the $animate.addClass function to add a class to my directive element after my data has been loaded. Worked like a charm. Would still like to see something a little less hacky, though...

Kaidanov commented 10 years ago

Is there some kind of feature that listens to the whole page dynamically rendered and the whole scopes are digested ? I have a dunamically built page with ng-repeats inside each other with multiple templates on one view ..How should I catch the rendered and scope is ready event ? Used $browser notifyWhenNoOutstandingRequests event but work only first time on loading the page.Second time ,it doesn't fill the context of my scope.

subdigit commented 10 years ago

I got what seems like a working solution from here: http://blog.brunoscopelliti.com/run-a-directive-after-the-dom-has-finished-rendering

As I understand it, it adds the post render function to the timeout queue which places it in a list of things to execute after the rendering engine has completed (which should already be in the queue busy executing away).

(Pretty new to angular so trying to wrap my head around things still so I may be way off base...)

zlf341 commented 10 years ago

@subdigit thank you, I tried $timeout, it works on me,

dcleao commented 10 years ago

What I think is being request here, is what angular uses internally as scope.$$postDigest(callback). This is what I would need to implement a "cloak" directive, that must run after all watches. If it exists, it is because there are good needs for it... So why not find some time to make a thing like this public?

chandermi commented 9 years ago

I want to render directive on drag of a control but it is not working it just adding the tag but not replacing html.Is there any solution for this?

NicoleY77 commented 9 years ago

$timeout + 1

gercheq commented 9 years ago

I need a similar feature as well. My goal is to be able to control the order of resources that get downloaded on page load. Since angularjs handles the directive rendering internally, there's no way to specify which directive gets higher priority in a controller. Let me explain with an example:

index.html

<!-- banner -->
<div hero-section></div>

<!-- recommendations -->
<div recommendations></div>

style.css

.hero-section { 
  background-image: url('hero-image.jpg');
}

.recommendations {
  background-image: url('recommendations-image.jpg')
}

If you want hero-image.jpg to load before the other one, good luck. I was able achieve this only by using $timeout inside the recommendations directive.

It'd be great to have a way of prioritizing which directives should be rendered first.

sebastianconcept commented 9 years ago

This is a really common need and pretty basic and intuitive. I don't get why the Angular team won't just emit an event. Anyways... here is what I did:


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

then you can do:

<div after-render></div>

or with any useful expression like:

<div after-render="$emit='onAfterThisThingRendered'"></div>
calidion commented 9 years ago

$viewContentFinished or $viewContentRendered event required.

aafloy commented 9 years ago

I think this is needed as well. I'm currently trying to measure the actual page load time and report this to application insights, and the events I've found so far does not cover everything.

I'm using http://angular-ui.github.com for the UI routing.

I've added logging to every event I could think of. The >>> and <<< is logging entries from a http interceptor. The other logging statements should be self explanatory.

For a page "Services" the results is:

 Services - $stateChangeStart
 >>> GET app/views/services.html
 <<< GET app/views/services.html
 Services - $stateChangeSuccess
 Services - $viewContentLoaded
 >>> GET <backendservice>/services
 Services - $locationChangeStart 
 Services - $locationChangeSuccess
 <<< GET <backendservice>/services

So I'm missing an event after the content of the services view is presented and bound on the client.

Any suggestions?

/Kenneth

katlimruiz commented 9 years ago

+1

I have also this problem, since I want to use masonry I can't because I dont have a place where the plugins injects into the DOM.

The summary of options I see:

  1. $viewContentLoaded + $timeout
  2. if you use your own directive, link + $timeout
  3. if the directive is 3rd party, use a transparent gif as a directive, and link + $timeout

I will try all of these.

hope this gets enabled somehow

fenduru commented 8 years ago

It's clear that this will never be addressed in angular 1.x, but I'll express my desire for this feature as well. It is impossible to do anything that requires measuring DOM elements in Angular. Even $$postDigest is insufficient because of the way ngAnimate works (it might add a $$postDigest handler after yours, and then also does logic in animation frames).

The main area I run into is trying to position a dropdown when it opens. If there are any ngIfs inside the dropdown content, the measurements made to do the positioning are invalidated when new content is transcluded.

basvd commented 8 years ago

Another solution that was not mentioned here has been found by SO-user joeforker.

It uses the same logic that Protactor needs to test an element after rendering.

hexinatgithub commented 8 years ago

Yes, this is a problem trouble me too.When i want to do something after angular controller finish add something to $scope and after compiled, I have no ideal how to do it.

mrded commented 7 years ago

@basvd it was mentioned above.

yaqzi commented 7 years ago

My solution for that kind of problems is angular.afterBindings: http://stacktrase.com/2017/02/05/angular-do-after-bindings/ It's brutal methode a bit, but... works :)

atidivya commented 7 years ago

Hello,

There are lot of examples for displaying charts on single page. What about if I need to display the chart on the next page.

Its like clicking the button on template 1 and showing the chart on template 2.?

JMCollins commented 7 years ago

I think what you want here is a way to asynchronously add a function with custom code to the end of Ionic's (or Angular's) processing queue. I used the Rxjs Observable timer to do this.

.ts file function toggleMap() { this.mapToggle = !this.mapToggle; var source = Observable.timer(0); var subscription = source.subscribe( x => { this.loadMap(); }); }

.html <div #map id="map" *ngIf="mapToggle"></div>

From : https://github.com/Reactive-Extensions/RxJS/blob/master/doc/api/core/operators/timeout.md

My use case was to show/hide a Google map in response to a user button click, so I used toggleMap above as the button's click handler function. I toggle a member variable which the *ngIf directive watches to create the HTML element for the map. Then, when the timeout completes, my loadMap function can access that element. If I were to try to call loadMap directly from the toggleMap function without the timeout, I would get an error that the map element is undefined.

Also, these need to be imported in the .ts file: import { Observable } from 'rxjs/Observable'; import 'rxjs/add/operator/timeout';

I hope this helps someone.

wiredearp commented 7 years ago

@JMCollins My gut feeling is that Angular uses this exact same "micro scheduling" scheme for almost anything these days and that you are just so lucky because your particular Angular rendering is simple (enough). If the rendering relies on nested ng-if or ng-repeat or even conditional attributes of some kind, here is what I believe will happen:

  1. Angular schedules an update that is pushed to a "queue" (somewhere in browser internals)
  2. You schedule an update that is pushed to the same queue.
  3. End of execution stack and the queue gets evaluated for updates. Yay!
  4. ... But the initial Angular update has now pushed 23 new updates to the queue :cry:
  5. End of the next execution stack, these get evaluated — way after your update

So the "end" of this queue is really a moving target because the Angular devs know about your trick and are using it for almost everything. Again, I have no idea if this is really the root of the problem, I get nervous when I study the code, but it can in theory explain the strange "quantum effects" you will observe when you attempt to compose Angular with other frameworks. If it is true, it would suit the developers to perform this stunt via a managed queue and then dispatch an event whenever nothing new gets pushed into it; preferably one that can be picked up outside of the Angular code, so like a custom DOM event or something.

JMCollins commented 7 years ago

@wiredearp All of what you said makes sense. I may well be really lucky to have a simple *ngIf condition and a fast-running custom function. However, I think the issue you bring up in a more complex scenario would require an event to fire when HTML DOM is done rendering, corresponding to item 5 in your post and not coincidentally the title of this thread. Then, one could perhaps re-schedule an asynchronous update to run custom code (again), item 2 in your post. My case uses the user's button click which occurs after the UI has rendered. Wouldn't a lifecycle hook like ngOnInit or ionViewDidEnter signal that rendering is done? It is also possible that the custom code could make dirty something that Angular is watching, resulting in a circular dependency(?) which would be infinite. Forgive me if I am over-simplifying, just food for thought.

destrofer commented 6 years ago

I know this thread is quite old, but for those who still have to work with old angular version, like myself I have a solution with no extra directives. At least it works fine for me.

var digestObserverValue = 0, digestObserverHandledValue  = -1;
var onBeforePaint = function() {
    // Called before painting
    digestObserverValue++; // Prepare for the next digest
    $(document.body).trigger("paint.ng");
    setTimeout(function() {
        // Called almost right after painting
        $(document.body).trigger("painted.ng");
    }, 0);
};
scope.$watch(function() {
    // Called on every digest.
    if( digestObserverHandledValue  !== digestObserverValue ) {
        // Executed only once per multiple sequential digest cycles (until $evalAsync and later
        // timeout/animation handlers are called).
        digestObserverHandledValue = digestObserverValue;
        scope.$evalAsync(function() {
            // Called after at least single digest cycle is finished, but before rendering.
            if( "requestAnimationFrame" in window ) {
                // Get onBeforePaint() called right before DOM styles are calculated and page is
                // painted.
                requestAnimationFrame(onBeforePaint);
            }
            else {
                // For older browser compatibility: get onBeforePaint() called before painting
                // when javascript engine finishes executing the current thread code and starts
                // handling expired timers (0ms timeout).
                setTimeout(onBeforePaint, 0);
            }
        });
    }
    return digestObserverValue;
}, function() {});

I add this code to the application controller. It uses jQuery to trigger paint.ng and painted.ng events on the document body.

hanoii commented 5 years ago

@destrofer can you explain a bit more how that would work? Also, where would you put code that should run exactly after everything was loaded?

destrofer commented 5 years ago

@hanoii, I can't really explain much more than is written in comments. The main trick is in putting digest detection code into watchExpression function of scope.$watch(), using scope.$evalAsync() to wait until all sequential digest cycles are finished, and using zero timeouts to trigger events as soon as javascript engine gets to handling timer events (which is usually right before rendering a frame). Since javascript timers are not asynchronous you don't have to worry about events getting triggered during digest cycles or while some other code is running. Variables digestObserverHandledValue and digestObserverValue are used to prevent triggering paint.ng and painted.ng events multiple times per single frame. In other words we first detect start of digest cycles ($watch), then wait till digest finishes ($evalAsync), then wait till browser gets to painting the frame (setTimeout/requestAnimationFrame), and then trigger our events in onBeforePaint().

As for usage: I put this code into controller initialization, but it should also work anywhere where you have access to scopes, like in directive linking function. I would recommend using this code in application controller because it is initialized only once.

myApp.controller('MainController', ['$scope', function(scope) {
    var digestObserverValue = 0, digestObserverHandledValue  = -1;
    var onBeforePaint = function() {
        // Called before painting
        digestObserverValue++; // Prepare for the next digest
        $(document.body).trigger("paint.ng");
        setTimeout(function() {
            // Called almost right after painting
            $(document.body).trigger("painted.ng");
        }, 0);
    };
    scope.$watch(function() {
        // Called on every digest.
        if( digestObserverHandledValue  !== digestObserverValue ) {
            // Executed only once per multiple sequential digest cycles (until $evalAsync and later
            // timeout/animation handlers are called).
            digestObserverHandledValue = digestObserverValue;
            scope.$evalAsync(function() {
                // Called after at least single digest cycle is finished, but before rendering.
                if( "requestAnimationFrame" in window ) {
                    // Get onBeforePaint() called right before DOM styles are calculated and page is
                    // painted.
                    requestAnimationFrame(onBeforePaint);
                }
                else {
                    // For older browser compatibility: get onBeforePaint() called before painting
                    // when javascript engine finishes executing the current thread code and starts
                    // handling expired timers (0ms timeout).
                    setTimeout(onBeforePaint, 0);
                }
            });
        }
        return digestObserverValue;
    }, function() {});
}]);

To catch this event you can simply handle DOM events paint.ng or painted.ng. For example I use painted.ng to reinitialize material library every time angular finishes rendering, because material library is unable to handle dynamically created elements:

<script type="text/javascript">
    $(document.body).on("painted.ng", function() {
        $.material.init();
    });
</script>
<div ng-controller="MainController">
    <table>
        <tr ng-repeat="item in items">
            <td>{{item.name}}</td>
            <td><input type="checkbox" ng-model="item.hidden" /></td>
        </tr>
    </table>
</div>

This was a simplified example of usage in javascript outside angular app, but you can use same code in your angular app too. Just remember that paint.ng and painted.ng events are triggered after digest cycle is finished. If you make any changes in the scope at that time you will have to call scope.$apply(). However calling scope.$apply() will start digest cycle again, which will cause paint.ng and painted.ng to be triggered again on the next frame. This will make an infinite loop of event triggering and digesting if you don't use any measures to prevent that.

I can't think of anything that would require to change scope only after element was rendered, but here is the example that would do so:

myApp.directive('greeting', function() {
    return {
        restrict: 'E',
        template: '{{message}}',
        link: function(scope, element, attrs, controllers) {
            scope.message = "Not rendered yet";
            var messageChanged = false;
            $(document.body).on("painted.ng", function() {
                if( !messageChanged ) {
                    messageChanged = true; // Prevent infinite loop
                    scope.message = "Hello world!";
                    scope.$apply();
                }
            });
        }
    };
});
<div ng-controller="MainController">
    <greeting></greeting>
</div>