-
Notifications
You must be signed in to change notification settings - Fork 24.8k
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
[perf] NgZone: runaway change detections #39348
Comments
@mleibman , I am not sure I understand the issue completely. So the case may look like.
|
We've actually hit this issue several times in different scenarios that are too specific to describe here, so I tried to come up with something simple and realistic. Ideally, this could be fixed in NgZone itself. Does CD have to be synchronous, or can it be scheduled to run at the end of a VM turn in a microtask? |
Currently there is an option
then all change detections caused by the |
I tried |
Yes, you are right, and sure I think we can use the similar approach, since currently |
…ge detections Close angular#39348 Now `NgZone` has an option `shouldCoalesceEventChangeDetection` to coalesce multiple event handler's change detections to one async change detection. And there are some cases other than `event handler` have the same issues. In angular#39348, the case like this. ``` // This code results in one change detection occurring per // ngZone.run() call. This is entirely feasible, and can be a serious // performance issue. for (let i = 0; i < 100; i++) { this.ngZone.run(() => { // do something }); } ``` So such kind of case will trigger multiple change detections. And now with Ivy, we have a new `markDirty()` API will schedule a requestAnimationFrame to trigger change detection and also coalesce the change detections in the same event loop, `markDirty()` API doesn't only take care `event handler` but also all other cases `sync/macroTask/..` So this PR add a new option to coalesce change detections for all cases.
…ge detections Close angular#39348 Now `NgZone` has an option `shouldCoalesceEventChangeDetection` to coalesce multiple event handler's change detections to one async change detection. And there are some cases other than `event handler` have the same issues. In angular#39348, the case like this. ``` // This code results in one change detection occurring per // ngZone.run() call. This is entirely feasible, and can be a serious // performance issue. for (let i = 0; i < 100; i++) { this.ngZone.run(() => { // do something }); } ``` So such kind of case will trigger multiple change detections. And now with Ivy, we have a new `markDirty()` API will schedule a requestAnimationFrame to trigger change detection and also coalesce the change detections in the same event loop, `markDirty()` API doesn't only take care `event handler` but also all other cases `sync/macroTask/..` So this PR add a new option to coalesce change detections for all cases.
…ge detections Close angular#39348 Now `NgZone` has an option `shouldCoalesceEventChangeDetection` to coalesce multiple event handler's change detections to one async change detection. And there are some cases other than `event handler` have the same issues. In angular#39348, the case like this. ``` // This code results in one change detection occurring per // ngZone.run() call. This is entirely feasible, and can be a serious // performance issue. for (let i = 0; i < 100; i++) { this.ngZone.run(() => { // do something }); } ``` So such kind of case will trigger multiple change detections. And now with Ivy, we have a new `markDirty()` API will schedule a requestAnimationFrame to trigger change detection and also coalesce the change detections in the same event loop, `markDirty()` API doesn't only take care `event handler` but also all other cases `sync/macroTask/..` So this PR add a new option to coalesce change detections for all cases.
…ge detections Close angular#39348 Now `NgZone` has an option `shouldCoalesceEventChangeDetection` to coalesce multiple event handler's change detections to one async change detection. And there are some cases other than `event handler` have the same issues. In angular#39348, the case like this. ``` // This code results in one change detection occurring per // ngZone.run() call. This is entirely feasible, and can be a serious // performance issue. for (let i = 0; i < 100; i++) { this.ngZone.run(() => { // do something }); } ``` So such kind of case will trigger multiple change detections. And now with Ivy, we have a new `markDirty()` API will schedule a requestAnimationFrame to trigger change detection and also coalesce the change detections in the same event loop, `markDirty()` API doesn't only take care `event handler` but also all other cases `sync/macroTask/..` So this PR add a new option to coalesce change detections for all cases.
…ge detections Close angular#39348 Now `NgZone` has an option `shouldCoalesceEventChangeDetection` to coalesce multiple event handler's change detections to one async change detection. And there are some cases other than `event handler` have the same issues. In angular#39348, the case like this. ``` // This code results in one change detection occurring per // ngZone.run() call. This is entirely feasible, and can be a serious // performance issue. for (let i = 0; i < 100; i++) { this.ngZone.run(() => { // do something }); } ``` So such kind of case will trigger multiple change detections. And now with Ivy, we have a new `markDirty()` API will schedule a requestAnimationFrame to trigger change detection and also coalesce the change detections in the same event loop, `markDirty()` API doesn't only take care `event handler` but also all other cases `sync/macroTask/..` So this PR add a new option to coalesce change detections for all cases.
…ge detections Close angular#39348 Now `NgZone` has an option `shouldCoalesceEventChangeDetection` to coalesce multiple event handler's change detections to one async change detection. And there are some cases other than `event handler` have the same issues. In angular#39348, the case like this. ``` // This code results in one change detection occurring per // ngZone.run() call. This is entirely feasible, and can be a serious // performance issue. for (let i = 0; i < 100; i++) { this.ngZone.run(() => { // do something }); } ``` So such kind of case will trigger multiple change detections. And now with Ivy, we have a new `markDirty()` API will schedule a requestAnimationFrame to trigger change detection and also coalesce the change detections in the same event loop, `markDirty()` API doesn't only take care `event handler` but also all other cases `sync/macroTask/..` So this PR add a new option to coalesce change detections for all cases.
…ge detections Close angular#39348 Now `NgZone` has an option `shouldCoalesceEventChangeDetection` to coalesce multiple event handler's change detections to one async change detection. And there are some cases other than `event handler` have the same issues. In angular#39348, the case like this. ``` // This code results in one change detection occurring per // ngZone.run() call. This is entirely feasible, and can be a serious // performance issue. for (let i = 0; i < 100; i++) { this.ngZone.run(() => { // do something }); } ``` So such kind of case will trigger multiple change detections. And now with Ivy, we have a new `markDirty()` API will schedule a requestAnimationFrame to trigger change detection and also coalesce the change detections in the same event loop, `markDirty()` API doesn't only take care `event handler` but also all other cases `sync/macroTask/..` So this PR add a new option to coalesce change detections for all cases.
…ge detections Close angular#39348 Now `NgZone` has an option `shouldCoalesceEventChangeDetection` to coalesce multiple event handler's change detections to one async change detection. And there are some cases other than `event handler` have the same issues. In angular#39348, the case like this. ``` // This code results in one change detection occurring per // ngZone.run() call. This is entirely feasible, and can be a serious // performance issue. for (let i = 0; i < 100; i++) { this.ngZone.run(() => { // do something }); } ``` So such kind of case will trigger multiple change detections. And now with Ivy, we have a new `markDirty()` API will schedule a requestAnimationFrame to trigger change detection and also coalesce the change detections in the same event loop, `markDirty()` API doesn't only take care `event handler` but also all other cases `sync/macroTask/..` So this PR add a new option to coalesce change detections for all cases.
…ge detections in the same event loop Close angular#39348 Now `NgZone` has an option `shouldCoalesceEventChangeDetection` to coalesce multiple event handler's change detections to one async change detection. And there are some cases other than `event handler` have the same issues. In angular#39348, the case like this. ``` // This code results in one change detection occurring per // ngZone.run() call. This is entirely feasible, and can be a serious // performance issue. for (let i = 0; i < 100; i++) { this.ngZone.run(() => { // do something }); } ``` So such kind of case will trigger multiple change detections. And now with Ivy, we have a new `markDirty()` API will schedule a requestAnimationFrame to trigger change detection and also coalesce the change detections in the same event loop, `markDirty()` API doesn't only take care `event handler` but also all other cases `sync/macroTask/..` So this PR add a new option to coalesce change detections for all cases.
I raised a similar question in the Angular Discord a couple weeks ago, but didn't get a good response. My question was- If I am running outside the Angular zone and want to re-enter Angular zone, say to just attach an event listener, how can this be done without triggering change detection? Because in this case, all I want to do is dynamically attach an event listener to an element inside Angular zone and there is absolutely no need for change detection to run immediately afterward (i.e. after calling I ended up writing this dirty helper function that patches out runWithoutTick<T>(fn: (...args: any[]) => T, appRef: ApplicationRef, ngZone: NgZone, applyThis?: any, applyArgs?: any[]): T {
const originalTick = appRef.tick;
try {
const patchedFunction = () => {
// First, call user function.
const x = fn.call(applyThis, applyArgs);
// Next, disable change detection after running user function, effectively making sure
// `ngZone.run()` does not trigger change detection.
appRef.tick = () => {};
return x;
};
return ngZone.run(patchedFunction, applyThis, applyArgs);
} finally {
// Finally, restore change detection.
appRef.tick = originalTick;
}
} @JiaLiPassion Is there a better or existing way to solve this problem? |
@Achilles1515 if all you want to do is attach an event listener that will run in NgZone, you can do that outside of NgZone, and just wrap the listener: // some code running outside of NgZone
// this will result in a change detection
this.ngZone.run(() => {
this.element.addEventListener('click', this.onClick);
});
// this does not result in a change detection, but the listener still runs in NgZone
this.element.addEventListener('click', () => {
this.ngZone.run(() => this.onClick);
}); |
…ge detections in the same event loop. Close angular#39348 Now `NgZone` has an option `shouldCoalesceEventChangeDetection` to coalesce multiple event handler's change detections to one async change detection. And there are some cases other than `event handler` have the same issues. In angular#39348, the case like this. ``` // This code results in one change detection occurring per // ngZone.run() call. This is entirely feasible, and can be a serious // performance issue. for (let i = 0; i < 100; i++) { this.ngZone.run(() => { // do something }); } ``` So such kind of case will trigger multiple change detections. And now with Ivy, we have a new `markDirty()` API will schedule a requestAnimationFrame to trigger change detection and also coalesce the change detections in the same event loop, `markDirty()` API doesn't only take care `event handler` but also all other cases `sync/macroTask/..` So this PR add a new option to coalesce change detections for all cases.
…ge detections in the same event loop. Close angular#39348 Now `NgZone` has an option `shouldCoalesceEventChangeDetection` to coalesce multiple event handler's change detections to one async change detection. And there are some cases other than `event handler` have the same issues. In angular#39348, the case like this. ``` // This code results in one change detection occurring per // ngZone.run() call. This is entirely feasible, and can be a serious // performance issue. for (let i = 0; i < 100; i++) { this.ngZone.run(() => { // do something }); } ``` So such kind of case will trigger multiple change detections. And now with Ivy, we have a new `markDirty()` API will schedule a requestAnimationFrame to trigger change detection and also coalesce the change detections in the same event loop, `markDirty()` API doesn't only take care `event handler` but also all other cases `sync/macroTask/..` So this PR add a new option to coalesce change detections for all cases.
…ge detections in the same event loop. Close angular#39348 Now `NgZone` has an option `shouldCoalesceEventChangeDetection` to coalesce multiple event handler's change detections to one async change detection. And there are some cases other than `event handler` have the same issues. In angular#39348, the case like this. ``` // This code results in one change detection occurring per // ngZone.run() call. This is entirely feasible, and can be a serious // performance issue. for (let i = 0; i < 100; i++) { this.ngZone.run(() => { // do something }); } ``` So such kind of case will trigger multiple change detections. And now with Ivy, we have a new `markDirty()` API will schedule a requestAnimationFrame to trigger change detection and also coalesce the change detections in the same event loop, `markDirty()` API doesn't only take care `event handler` but also all other cases `sync/macroTask/..` So this PR add a new option to coalesce change detections for all cases.
@mleibman For that example, that is a good solution, assuming I am in charge of the callback code. But it was more just a general statement - when going from outside Angular zone to inside Angular zone and back out, why is a change detection cycle always necessary (and forced)? |
…ge detections in the same event loop. Close angular#39348 Now `NgZone` has an option `shouldCoalesceEventChangeDetection` to coalesce multiple event handler's change detections to one async change detection. And there are some cases other than `event handler` have the same issues. In angular#39348, the case like this. ``` // This code results in one change detection occurring per // ngZone.run() call. This is entirely feasible, and can be a serious // performance issue. for (let i = 0; i < 100; i++) { this.ngZone.run(() => { // do something }); } ``` So such kind of case will trigger multiple change detections. And now with Ivy, we have a new `markDirty()` API will schedule a requestAnimationFrame to trigger change detection and also coalesce the change detections in the same event loop, `markDirty()` API doesn't only take care `event handler` but also all other cases `sync/macroTask/..` So this PR add a new option to coalesce change detections for all cases.
…hange detections in the same event loop. Close angular#39348 Now `NgZone` has an option `shouldCoalesceEventChangeDetection` to coalesce multiple event handler's change detections to one async change detection. And there are some cases other than `event handler` have the same issues. In angular#39348, the case like this. ``` // This code results in one change detection occurring per // ngZone.run() call. This is entirely feasible, and can be a serious // performance issue. for (let i = 0; i < 100; i++) { this.ngZone.run(() => { // do something }); } ``` So such kind of case will trigger multiple change detections. And now with Ivy, we have a new `markDirty()` API will schedule a requestAnimationFrame to trigger change detection and also coalesce the change detections in the same event loop, `markDirty()` API doesn't only take care `event handler` but also all other cases `sync/macroTask/..` So this PR add a new option to coalesce change detections for all cases.
…hange detections in the same event loop. Close angular#39348 Now `NgZone` has an option `shouldCoalesceEventChangeDetection` to coalesce multiple event handler's change detections to one async change detection. And there are some cases other than `event handler` have the same issues. In angular#39348, the case like this. ``` // This code results in one change detection occurring per // ngZone.run() call. This is entirely feasible, and can be a serious // performance issue. for (let i = 0; i < 100; i++) { this.ngZone.run(() => { // do something }); } ``` So such kind of case will trigger multiple change detections. And now with Ivy, we have a new `markDirty()` API will schedule a requestAnimationFrame to trigger change detection and also coalesce the change detections in the same event loop, `markDirty()` API doesn't only take care `event handler` but also all other cases `sync/macroTask/..` So this PR add a new option to coalesce change detections for all cases.
…hange detections in the same event loop. Close angular#39348 Now `NgZone` has an option `shouldCoalesceEventChangeDetection` to coalesce multiple event handler's change detections to one async change detection. And there are some cases other than `event handler` have the same issues. In angular#39348, the case like this. ``` // This code results in one change detection occurring per // ngZone.run() call. This is entirely feasible, and can be a serious // performance issue. for (let i = 0; i < 100; i++) { this.ngZone.run(() => { // do something }); } ``` So such kind of case will trigger multiple change detections. And now with Ivy, we have a new `markDirty()` API will schedule a requestAnimationFrame to trigger change detection and also coalesce the change detections in the same event loop, `markDirty()` API doesn't only take care `event handler` but also all other cases `sync/macroTask/..` So this PR add a new option to coalesce change detections for all cases.
…hange detections in the same event loop. Close angular#39348 Now `NgZone` has an option `shouldCoalesceEventChangeDetection` to coalesce multiple event handler's change detections to one async change detection. And there are some cases other than `event handler` have the same issues. In angular#39348, the case like this. ``` // This code results in one change detection occurring per // ngZone.run() call. This is entirely feasible, and can be a serious // performance issue. for (let i = 0; i < 100; i++) { this.ngZone.run(() => { // do something }); } ``` So such kind of case will trigger multiple change detections. And now with Ivy, we have a new `markDirty()` API will schedule a requestAnimationFrame to trigger change detection and also coalesce the change detections in the same event loop, `markDirty()` API doesn't only take care `event handler` but also all other cases `sync/macroTask/..` So this PR add a new option to coalesce change detections for all cases.
…hange detections in the same event loop. Close angular#39348 Now `NgZone` has an option `shouldCoalesceEventChangeDetection` to coalesce multiple event handler's change detections to one async change detection. And there are some cases other than `event handler` have the same issues. In angular#39348, the case like this. ``` // This code results in one change detection occurring per // ngZone.run() call. This is entirely feasible, and can be a serious // performance issue. for (let i = 0; i < 100; i++) { this.ngZone.run(() => { // do something }); } ``` So such kind of case will trigger multiple change detections. And now with Ivy, we have a new `markDirty()` API will schedule a requestAnimationFrame to trigger change detection and also coalesce the change detections in the same event loop, `markDirty()` API doesn't only take care `event handler` but also all other cases `sync/macroTask/..` So this PR add a new option to coalesce change detections for all cases. test(core): add test case for shouldCoalesceEventChangeDetection option Add new test cases for current `shouldCoalesceEventChangeDetection` in `ng_zone.spec`, since currently we only have integration test for this one.
…hange detections in the same event loop. Close angular#39348 Now `NgZone` has an option `shouldCoalesceEventChangeDetection` to coalesce multiple event handler's change detections to one async change detection. And there are some cases other than `event handler` have the same issues. In angular#39348, the case like this. ``` // This code results in one change detection occurring per // ngZone.run() call. This is entirely feasible, and can be a serious // performance issue. for (let i = 0; i < 100; i++) { this.ngZone.run(() => { // do something }); } ``` So such kind of case will trigger multiple change detections. And now with Ivy, we have a new `markDirty()` API will schedule a requestAnimationFrame to trigger change detection and also coalesce the change detections in the same event loop, `markDirty()` API doesn't only take care `event handler` but also all other cases `sync/macroTask/..` So this PR add a new option to coalesce change detections for all cases. test(core): add test case for shouldCoalesceEventChangeDetection option Add new test cases for current `shouldCoalesceEventChangeDetection` in `ng_zone.spec`, since currently we only have integration test for this one.
…hange detections in the same event loop. Close angular#39348 Now `NgZone` has an option `shouldCoalesceEventChangeDetection` to coalesce multiple event handler's change detections to one async change detection. And there are some cases other than `event handler` have the same issues. In angular#39348, the case like this. ``` // This code results in one change detection occurring per // ngZone.run() call. This is entirely feasible, and can be a serious // performance issue. for (let i = 0; i < 100; i++) { this.ngZone.run(() => { // do something }); } ``` So such kind of case will trigger multiple change detections. And now with Ivy, we have a new `markDirty()` API will schedule a requestAnimationFrame to trigger change detection and also coalesce the change detections in the same event loop, `markDirty()` API doesn't only take care `event handler` but also all other cases `sync/macroTask/..` So this PR add a new option to coalesce change detections for all cases. test(core): add test case for shouldCoalesceEventChangeDetection option Add new test cases for current `shouldCoalesceEventChangeDetection` in `ng_zone.spec`, since currently we only have integration test for this one.
…hange detections in the same event loop. Close angular#39348 Now `NgZone` has an option `shouldCoalesceEventChangeDetection` to coalesce multiple event handler's change detections to one async change detection. And there are some cases other than `event handler` have the same issues. In angular#39348, the case like this. ``` // This code results in one change detection occurring per // ngZone.run() call. This is entirely feasible, and can be a serious // performance issue. for (let i = 0; i < 100; i++) { this.ngZone.run(() => { // do something }); } ``` So such kind of case will trigger multiple change detections. And now with Ivy, we have a new `markDirty()` API will schedule a requestAnimationFrame to trigger change detection and also coalesce the change detections in the same event loop, `markDirty()` API doesn't only take care `event handler` but also all other cases `sync/macroTask/..` So this PR add a new option to coalesce change detections for all cases. test(core): add test case for shouldCoalesceEventChangeDetection option Add new test cases for current `shouldCoalesceEventChangeDetection` in `ng_zone.spec`, since currently we only have integration test for this one.
…hange detections in the same event loop. Close angular#39348 Now `NgZone` has an option `shouldCoalesceEventChangeDetection` to coalesce multiple event handler's change detections to one async change detection. And there are some cases other than `event handler` have the same issues. In angular#39348, the case like this. ``` // This code results in one change detection occurring per // ngZone.run() call. This is entirely feasible, and can be a serious // performance issue. for (let i = 0; i < 100; i++) { this.ngZone.run(() => { // do something }); } ``` So such kind of case will trigger multiple change detections. And now with Ivy, we have a new `markDirty()` API will schedule a requestAnimationFrame to trigger change detection and also coalesce the change detections in the same event loop, `markDirty()` API doesn't only take care `event handler` but also all other cases `sync/macroTask/..` So this PR add a new option to coalesce change detections for all cases. test(core): add test case for shouldCoalesceEventChangeDetection option Add new test cases for current `shouldCoalesceEventChangeDetection` in `ng_zone.spec`, since currently we only have integration test for this one.
This issue has been automatically locked due to inactivity. Read more about our automatic conversation locking policy. This action has been performed automatically by a bot. |
Summary
Using
NgZone.run()
can in some cases result in a large number of change detections performed synchronously, creating a serious performance issue.Repro
https://stackblitz.com/edit/angular-5zht6w
Description
NgZone
runs change detection as soon as the execution leaves the top-mostNgZone
call and all the microtasks there have finished, but if there are more than one top-level calls toNgZone.run()
in a single VM turn, a synchronous change detection is run after each one.The example above may seem somewhat contrived, but this situation is quite easy to get into w/o even noticing. For example, you may have a
window.onmessage
event handler that receives data from a worker or anIFRAME
. The handler then iterates through the array of received objects, invoking various callbacks or emitting observable values for each one. If one of those callbacks or subscribers is part of an Angular app and needs to update something that should be picked up by Angular's change detection, they would wrap it in theNgZone.run()
call.The text was updated successfully, but these errors were encountered: