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?

hanoii commented 5 years ago

@destrofer, thanks for the write up - truly appreciate. Unfortunately this didn't work for me. I am working on a complex angular app with a lot of different async events that can affect the DOM and I needed a way to reliable detect the last possible moment in the cycle of the page loading, but I haven't yet found a way to do it.

On your logic, the paint event is called many times, and then indefinitely as each time evalSync finishes it called onbefore pain which changes the variable which is watched. I guess in your use case it was good enough to detect it the first time, but I really need it to be once and exactly at the last possible time. On your example, it seems $.material.init(); will be called all the time on each watch interval.

hanoii commented 5 years ago

I also tried a similar approach of detecting the digest cicle, but it's no point, as evalAsync always add a digest cycle to it. Further more, a watch works of the interval of digest cycles.

destrofer commented 5 years ago

@hanoii, Yes, evalAsync does add a digest cycle, however if( digestObserverHandledValue !== digestObserverValue ) prevents evalAsync from getting called again in that extra cycle. That is possible only because digestObserverValue variable is updated only in onBeforePaint function, which is called only when timeout (setTimeout) or animation frame (requestAnimationFrame) event is handled by javascript engine, which in turn happens only after all sequential digest cycles have finished. This means that next time evalAsync/onBeforePaint will be called only after something else triggers digest again.

The only exception I can think of, why it would not work as expected would be if javascript engine is fully asynchronous. That is when timeout/animation frame event handlers are called in a separate thread while angular digest cycles are still running. I checked it on Edge, FireFox, Opera and Chrome, and they did wait till digest cycles were stopped.

but I really need it to be once and exactly at the last possible time

I guess it is not possible by using my suggested algorithm since angular may re-render HTML DOM on multiple occasions. But at least I suggested something that is actually related to the topic of this issue :)

ibrychgo commented 3 years ago

@destrofer - THANK YOU. This is the only method that reliably worked on our really really complicated nested page of ng-repeats. I simply modified your code to turn off our "loading progress" when "painted" and all is well. No other modifications were needed.