Skip to content

Commit

Permalink
fix(core): use setTimeout when coalescing tasks in Node.js (#50820)
Browse files Browse the repository at this point in the history
This commit updates the implementation of the `getNativeRequestAnimationFrame`
and checks whether the current code runs in the browser before retrieving
`requestAnimationFrame`. `requestAnimationFrame` is not available when the code
is running in the Node.js environment. We have to fallback to `setTimeout` for
delaying the change detection.

PR Close #50820
  • Loading branch information
arturovt authored and alxhub committed Jun 30, 2023
1 parent 00f0149 commit b66a16e
Showing 1 changed file with 24 additions and 4 deletions.
28 changes: 24 additions & 4 deletions packages/core/src/util/raf.ts
Expand Up @@ -5,15 +5,35 @@
* Use of this source code is governed by an MIT-style license that can be
* found in the LICENSE file at https://angular.io/license
*/

import {global} from './global';

export function getNativeRequestAnimationFrame() {
// Note: the `getNativeRequestAnimationFrame` is used in the `NgZone` class, but we cannot use the
// `inject` function. The `NgZone` instance may be created manually, and thus the injection
// context will be unavailable. This might be enough to check whether `requestAnimationFrame` is
// available because otherwise, we'll fall back to `setTimeout`.
const isBrowser = typeof global['requestAnimationFrame'] === 'function';

// Note: `requestAnimationFrame` is unavailable when the code runs in the Node.js environment. We
// use `setTimeout` because no changes are required other than checking if the current platform is
// the browser. `setTimeout` is a well-established API that is available in both environments.
// `requestAnimationFrame` is used in the browser to coalesce event tasks since event tasks are
// usually executed within the same rendering frame (but this is more implementation details of
// browsers).
let nativeRequestAnimationFrame: (callback: FrameRequestCallback) => number =
global['requestAnimationFrame'];
let nativeCancelAnimationFrame: (handle: number) => void = global['cancelAnimationFrame'];
global[isBrowser ? 'requestAnimationFrame' : 'setTimeout'];

let nativeCancelAnimationFrame: (handle: number) => void =
global[isBrowser ? 'cancelAnimationFrame' : 'clearTimeout'];

if (typeof Zone !== 'undefined' && nativeRequestAnimationFrame! && nativeCancelAnimationFrame!) {
// use unpatched version of requestAnimationFrame(native delegate) if possible
// to avoid another Change detection
// Note: zone.js sets original implementations on patched APIs behind the
// `__zone_symbol__OriginalDelegate` key (see `attachOriginToPatched`). Given the following
// example: `window.requestAnimationFrame.__zone_symbol__OriginalDelegate`; this would return an
// unpatched implementation of the `requestAnimationFrame`, which isn't intercepted by the
// Angular zone. We use the unpatched implementation to avoid another change detection when
// coalescing tasks.
const unpatchedRequestAnimationFrame =
(nativeRequestAnimationFrame as any)[(Zone as any).__symbol__('OriginalDelegate')];
if (unpatchedRequestAnimationFrame) {
Expand Down

0 comments on commit b66a16e

Please sign in to comment.