New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

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

Closed
kstep opened this Issue Jan 25, 2012 · 47 comments

Comments

Projects
None yet
@kstep
Contributor

kstep commented Jan 25, 2012

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

This comment has been minimized.

Show comment
Hide comment
@IgorMinar

IgorMinar Jan 25, 2012

Member

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?

Member

IgorMinar commented Jan 25, 2012

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

This comment has been minimized.

Show comment
Hide comment
@mhevery

mhevery Jan 25, 2012

Member

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:
#734

Member

mhevery commented Jan 25, 2012

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:
#734

@kstep

This comment has been minimized.

Show comment
Hide comment
@kstep

kstep Jan 25, 2012

Contributor

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

Contributor

kstep commented Jan 25, 2012

$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

This comment has been minimized.

Show comment
Hide comment
@kstep

kstep Jan 25, 2012

Contributor

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.

Contributor

kstep commented Jan 25, 2012

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

This comment has been minimized.

Show comment
Hide comment
@mhevery

mhevery Jan 25, 2012

Member

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:
#734 (comment)

Member

mhevery commented Jan 25, 2012

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:
#734 (comment)

@kstep

This comment has been minimized.

Show comment
Hide comment
@kstep

kstep Jan 26, 2012

Contributor

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.

Contributor

kstep commented Jan 26, 2012

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

This comment has been minimized.

Show comment
Hide comment
@mhevery

mhevery Jan 26, 2012

Member

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:
#734 (comment)

Member

mhevery commented Jan 26, 2012

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:
#734 (comment)

@IgorMinar

This comment has been minimized.

Show comment
Hide comment
@IgorMinar

IgorMinar Mar 20, 2012

Member

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.

Member

IgorMinar commented Mar 20, 2012

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.

@IgorMinar IgorMinar closed this Mar 20, 2012

@eprouver

This comment has been minimized.

Show comment
Hide comment
@eprouver

eprouver Jul 27, 2012

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/

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

This comment has been minimized.

Show comment
Hide comment
@paramburu

paramburu Aug 1, 2012

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.

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

This comment has been minimized.

Show comment
Hide comment
@kstep

kstep Aug 1, 2012

Contributor

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

Contributor

kstep commented Aug 1, 2012

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

@kstep

This comment has been minimized.

Show comment
Hide comment
Contributor

kstep commented Aug 1, 2012

@eprouver

This comment has been minimized.

Show comment
Hide comment
@eprouver

eprouver Aug 1, 2012

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/

eprouver commented Aug 1, 2012

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

This comment has been minimized.

Show comment
Hide comment
@kstep

kstep Aug 1, 2012

Contributor

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

Contributor

kstep commented Aug 1, 2012

@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

This comment has been minimized.

Show comment
Hide comment
@eprouver

eprouver Aug 1, 2012

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

eprouver commented Aug 1, 2012

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

@abdulazeezsk

This comment has been minimized.

Show comment
Hide comment
@abdulazeezsk

abdulazeezsk Oct 6, 2012

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

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

This comment has been minimized.

Show comment
Hide comment
@todiadiyatmo

todiadiyatmo Nov 11, 2012

@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
@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

This comment has been minimized.

Show comment
Hide comment
@kstep

kstep Nov 15, 2012

Contributor

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

Contributor

kstep commented Nov 15, 2012

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

This comment has been minimized.

Show comment
Hide comment
@erkulas

erkulas Mar 20, 2013

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.

erkulas commented Mar 20, 2013

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

This comment has been minimized.

Show comment
Hide comment
@mokesmokes

mokesmokes Apr 13, 2013

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.

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

This comment has been minimized.

Show comment
Hide comment
@wuxiaoying

wuxiaoying Aug 6, 2013

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.

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

This comment has been minimized.

Show comment
Hide comment
@eprouver

eprouver Aug 6, 2013

@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/

eprouver commented Aug 6, 2013

@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

This comment has been minimized.

Show comment
Hide comment
@wuxiaoying

wuxiaoying Aug 6, 2013

@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 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

This comment has been minimized.

Show comment
Hide comment
@eprouver

eprouver Aug 6, 2013

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

eprouver commented Aug 6, 2013

@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

This comment has been minimized.

Show comment
Hide comment
@wuxiaoying

wuxiaoying Aug 6, 2013

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

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

This comment has been minimized.

Show comment
Hide comment
@LukeStone

LukeStone Sep 9, 2013

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.

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

This comment has been minimized.

Show comment
Hide comment
@onethread

onethread Feb 14, 2014

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

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

This comment has been minimized.

Show comment
Hide comment
@Kaidanov

Kaidanov Apr 2, 2014

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.

Kaidanov commented Apr 2, 2014

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

This comment has been minimized.

Show comment
Hide comment
@subdigit

subdigit Apr 12, 2014

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

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

This comment has been minimized.

Show comment
Hide comment
@zlf341

zlf341 Aug 21, 2014

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

zlf341 commented Aug 21, 2014

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

@dcleao

This comment has been minimized.

Show comment
Hide comment
@dcleao

dcleao Sep 4, 2014

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?

dcleao commented Sep 4, 2014

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

This comment has been minimized.

Show comment
Hide comment
@chandermi

chandermi Nov 26, 2014

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?

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?

@NecoleYu

This comment has been minimized.

Show comment
Hide comment
@NecoleYu

NecoleYu Dec 24, 2014

$timeout + 1

$timeout + 1

@gercheq

This comment has been minimized.

Show comment
Hide comment
@gercheq

gercheq Feb 23, 2015

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.

gercheq commented Feb 23, 2015

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

This comment has been minimized.

Show comment
Hide comment
@sebastianconcept

sebastianconcept Mar 11, 2015

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>

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

This comment has been minimized.

Show comment
Hide comment
@calidion

calidion Apr 24, 2015

$viewContentFinished or $viewContentRendered event required.

$viewContentFinished or $viewContentRendered event required.

@kaafloy

This comment has been minimized.

Show comment
Hide comment
@kaafloy

kaafloy May 30, 2015

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

kaafloy commented May 30, 2015

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

This comment has been minimized.

Show comment
Hide comment
@katlimruiz

katlimruiz Aug 11, 2015

+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

+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

This comment has been minimized.

Show comment
Hide comment
@fenduru

fenduru Nov 13, 2015

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.

fenduru commented Nov 13, 2015

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

This comment has been minimized.

Show comment
Hide comment
@basvd

basvd Feb 25, 2016

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.

basvd commented Feb 25, 2016

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

This comment has been minimized.

Show comment
Hide comment
@hexinatgithub

hexinatgithub Apr 17, 2016

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.

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

This comment has been minimized.

Show comment
Hide comment
@mrded

mrded Nov 17, 2016

@basvd it was mentioned above.

mrded commented Nov 17, 2016

@basvd it was mentioned above.

@yaqzi

This comment has been minimized.

Show comment
Hide comment
@yaqzi

yaqzi Feb 10, 2017

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

yaqzi commented Feb 10, 2017

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

This comment has been minimized.

Show comment
Hide comment
@atidivya

atidivya Feb 15, 2017

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

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

This comment has been minimized.

Show comment
Hide comment
@JMCollins

JMCollins Apr 25, 2017

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.

JMCollins commented Apr 25, 2017

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

This comment has been minimized.

Show comment
Hide comment
@wiredearp

wiredearp Apr 26, 2017

@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 😢
  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.

wiredearp commented Apr 26, 2017

@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 😢
  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

This comment has been minimized.

Show comment
Hide comment
@JMCollins

JMCollins Apr 26, 2017

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

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

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment