Skip to content

Commit b92c8ba

Browse files
fix(core): update animation scheduling
In some rare cases, it seems the animation queue disappears despite being afterEveryRender. This updates the animation scheduler to be afterNextRender instead and only schedules it when we need to. fixes: #64423
1 parent 3d1777b commit b92c8ba

File tree

12 files changed

+132
-51
lines changed

12 files changed

+132
-51
lines changed

packages/core/src/animation/interfaces.ts

Lines changed: 0 additions & 21 deletions
Original file line numberDiff line numberDiff line change
@@ -18,27 +18,6 @@ export const ANIMATIONS_DISABLED = new InjectionToken<boolean>(
1818
},
1919
);
2020

21-
export interface AnimationQueue {
22-
queue: Set<Function>;
23-
isScheduled: boolean;
24-
}
25-
26-
/**
27-
* A [DI token](api/core/InjectionToken) for the queue of all animations.
28-
*/
29-
export const ANIMATION_QUEUE = new InjectionToken<AnimationQueue>(
30-
typeof ngDevMode !== 'undefined' && ngDevMode ? 'AnimationQueue' : '',
31-
{
32-
providedIn: 'root',
33-
factory: () => {
34-
return {
35-
queue: new Set(),
36-
isScheduled: false,
37-
};
38-
},
39-
},
40-
);
41-
4221
/**
4322
* The event type for when `animate.enter` and `animate.leave` are used with function
4423
* callbacks.
Lines changed: 54 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,54 @@
1+
/**
2+
* @license
3+
* Copyright Google LLC All Rights Reserved.
4+
*
5+
* Use of this source code is governed by an MIT-style license that can be
6+
* found in the LICENSE file at https://angular.dev/license
7+
*/
8+
9+
import {afterNextRender} from '../render3/after_render/hooks';
10+
import {InjectionToken, Injector} from '../di';
11+
12+
export interface AnimationQueue {
13+
queue: Set<Function>;
14+
isScheduled: boolean;
15+
}
16+
17+
/**
18+
* A [DI token](api/core/InjectionToken) for the queue of all animations.
19+
*/
20+
export const ANIMATION_QUEUE = new InjectionToken<AnimationQueue>(
21+
typeof ngDevMode !== 'undefined' && ngDevMode ? 'AnimationQueue' : '',
22+
{
23+
providedIn: 'root',
24+
factory: () => {
25+
return {
26+
queue: new Set(),
27+
isScheduled: false,
28+
};
29+
},
30+
},
31+
);
32+
33+
export function scheduleAnimationQueue(injector: Injector) {
34+
const animationQueue = injector.get(ANIMATION_QUEUE);
35+
// We only want to schedule the animation queue if it hasn't already been scheduled.
36+
if (!animationQueue.isScheduled) {
37+
afterNextRender(
38+
() => {
39+
animationQueue.isScheduled = false;
40+
runQueuedAnimations(injector);
41+
},
42+
{injector},
43+
);
44+
animationQueue.isScheduled = true;
45+
}
46+
}
47+
48+
function runQueuedAnimations(injector: Injector) {
49+
const animationQueue = injector.get(ANIMATION_QUEUE);
50+
for (let animateFn of animationQueue.queue) {
51+
animateFn();
52+
}
53+
animationQueue.queue.clear();
54+
}

packages/core/src/render3/instructions/animation.ts

Lines changed: 4 additions & 28 deletions
Original file line numberDiff line numberDiff line change
@@ -7,11 +7,11 @@
77
*/
88

99
import {
10-
ANIMATION_QUEUE,
1110
AnimationCallbackEvent,
1211
AnimationFunction,
1312
MAX_ANIMATION_TIMEOUT,
1413
} from '../../animation/interfaces';
14+
import {ANIMATION_QUEUE, scheduleAnimationQueue} from '../../animation/queue';
1515
import {getLView, getCurrentTNode} from '../state';
1616
import {RENDERER, INJECTOR, CONTEXT, LView, ANIMATIONS} from '../interfaces/view';
1717
import {getNativeByTNode} from '../util/view_utils';
@@ -21,8 +21,6 @@ import {NgZone} from '../../zone';
2121
import {determineLongestAnimation, allLeavingAnimations} from '../../animation/longest_animation';
2222
import {TNode} from '../interfaces/node';
2323
import {promiseWithResolvers} from '../../util/promise_with_resolvers';
24-
import {Injector} from '../../di';
25-
import {afterEveryRender} from '../after_render/hooks';
2624

2725
import {
2826
addAnimationToLView,
@@ -244,7 +242,7 @@ export function ɵɵanimateLeave(value: string | Function): typeof ɵɵanimateLe
244242
runLeaveAnimations(lView, tNode, value),
245243
);
246244

247-
enableAnimationQueueScheduler(lView[INJECTOR]);
245+
scheduleAnimationQueue(lView[INJECTOR]);
248246

249247
return ɵɵanimateLeave; // For chaining
250248
}
@@ -377,7 +375,7 @@ export function ɵɵanimateLeaveListener(value: AnimationFunction): typeof ɵɵa
377375
runLeaveAnimationFunction(lView, tNode, value),
378376
);
379377

380-
enableAnimationQueueScheduler(lView[INJECTOR]);
378+
scheduleAnimationQueue(lView[INJECTOR]);
381379

382380
return ɵɵanimateLeaveListener; // For chaining
383381
}
@@ -467,7 +465,6 @@ function runLeaveAnimationFunction(
467465
}
468466

469467
function queueEnterAnimations(lView: LView) {
470-
enableAnimationQueueScheduler(lView[INJECTOR]);
471468
const enterAnimations = lView[ANIMATIONS]?.enter;
472469
if (enterAnimations) {
473470
const animationQueue = lView[INJECTOR].get(ANIMATION_QUEUE);
@@ -476,27 +473,6 @@ function queueEnterAnimations(lView: LView) {
476473
animationQueue.queue.add(animateFn);
477474
}
478475
}
476+
scheduleAnimationQueue(lView[INJECTOR]);
479477
}
480478
}
481-
482-
function enableAnimationQueueScheduler(injector: Injector) {
483-
const animationQueue = injector.get(ANIMATION_QUEUE);
484-
// We only need to schedule the animation queue runner once per application.
485-
if (!animationQueue.isScheduled) {
486-
afterEveryRender(
487-
() => {
488-
runQueuedAnimations(injector);
489-
},
490-
{injector},
491-
);
492-
animationQueue.isScheduled = true;
493-
}
494-
}
495-
496-
function runQueuedAnimations(injector: Injector) {
497-
const animationQueue = injector.get(ANIMATION_QUEUE);
498-
for (let animateFn of animationQueue.queue) {
499-
animateFn();
500-
}
501-
animationQueue.queue.clear();
502-
}

packages/core/src/render3/node_manipulation.ts

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -82,8 +82,8 @@ import {profiler} from './profiler';
8282
import {ProfilerEvent} from './profiler_types';
8383
import {getLViewParent, getNativeByTNode, unwrapRNode} from './util/view_utils';
8484
import {allLeavingAnimations} from '../animation/longest_animation';
85-
import {ANIMATION_QUEUE} from '../animation/interfaces';
8685
import {Injector} from '../di';
86+
import {scheduleAnimationQueue, ANIMATION_QUEUE} from '../animation/queue';
8787

8888
const enum WalkTNodeTreeAction {
8989
/** node create in the native environment. Run on initial creation. */
@@ -114,6 +114,7 @@ function maybeQueueEnterAnimation(
114114
for (const animateFn of enterAnimations.get(tNode.index)!.animateFns) {
115115
animationQueue.queue.add(animateFn);
116116
}
117+
scheduleAnimationQueue(injector);
117118
}
118119
}
119120

@@ -431,6 +432,7 @@ function runLeaveAnimationsWithCallback(
431432
callback(false);
432433
}
433434
});
435+
scheduleAnimationQueue(injector);
434436
}
435437

436438
function runAfterLeaveAnimations(lView: LView, callback: Function) {

packages/core/test/acceptance/view_container_ref_spec.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -48,7 +48,7 @@ import {ComponentFixture, TestBed, TestComponentRenderer} from '../../testing';
4848
import {clearTranslations, loadTranslations} from '@angular/localize';
4949
import {By, DomSanitizer} from '@angular/platform-browser';
5050
import {expect} from '@angular/private/testing/matchers';
51-
import {ANIMATION_QUEUE} from '../../src/animation/interfaces';
51+
import {ANIMATION_QUEUE} from '../../src/animation/queue';
5252

5353
describe('ViewContainerRef', () => {
5454
/**

packages/core/test/bundling/animations-standalone/bundle.golden_symbols.json

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,7 @@
11
{
22
"chunks": {
33
"main": [
4+
"AFTER_RENDER_PHASES",
45
"AFTER_RENDER_SEQUENCES_TO_ADD",
56
"ANIMATIONS",
67
"ANIMATION_MODULE_TYPE",
@@ -12,7 +13,9 @@
1213
"APP_ID_ATTRIBUTE_NAME",
1314
"APP_INITIALIZER",
1415
"AUTO_STYLE",
16+
"AfterRenderImpl",
1517
"AfterRenderManager",
18+
"AfterRenderSequence",
1619
"AnimationAstBuilderContext",
1720
"AnimationAstBuilderVisitor",
1821
"AnimationDriver",
@@ -263,6 +266,7 @@
263266
"UnsubscriptionError",
264267
"VIEW_REFS",
265268
"VOID_VALUE",
269+
"ViewContext",
266270
"ViewEncapsulation",
267271
"ViewRef",
268272
"WebAnimationsDriver",
@@ -324,6 +328,8 @@
324328
"addServerStyles",
325329
"addToAnimationQueue",
326330
"addToEndOfViewTree",
331+
"afterEveryRenderImpl",
332+
"afterNextRender",
327333
"allLeavingAnimations",
328334
"allocExpando",
329335
"allocLFrame",
@@ -503,6 +509,7 @@
503509
"getFactoryDef",
504510
"getFirstLContainer",
505511
"getGlobalLocale",
512+
"getHooks",
506513
"getInheritedInjectableDef",
507514
"getInitialLViewFlagsFromDef",
508515
"getInjectFlag",
@@ -579,6 +586,7 @@
579586
"injectElementRef",
580587
"injectInjectorOnly",
581588
"injectRootLimpMode",
589+
"injectViewContext",
582590
"injectableDefOrInjectorDefFactory",
583591
"insertBloom",
584592
"instantiateAllDirectives",
@@ -785,8 +793,10 @@
785793
"runInInjectionContext",
786794
"runLeaveAnimationsWithCallback",
787795
"runPlatformInitializers",
796+
"runQueuedAnimations",
788797
"saveNameToExportMap",
789798
"saveResolvedLocalsInData",
799+
"scheduleAnimationQueue",
790800
"scheduleCallbackWithMicrotask",
791801
"scheduleCallbackWithRafRace",
792802
"searchTokensOnInjector",

packages/core/test/bundling/defer/bundle.golden_symbols.json

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -58,13 +58,16 @@
5858
"shimStylesContent"
5959
],
6060
"lazy": [
61+
"AFTER_RENDER_PHASES",
6162
"AFTER_RENDER_SEQUENCES_TO_ADD",
6263
"ANIMATIONS",
6364
"ANIMATION_QUEUE",
6465
"APP_BOOTSTRAP_LISTENER",
6566
"APP_ID",
6667
"APP_INITIALIZER",
68+
"AfterRenderImpl",
6769
"AfterRenderManager",
70+
"AfterRenderSequence",
6871
"AnonymousSubject",
6972
"ApplicationInitStatus",
7073
"ApplicationRef",
@@ -241,6 +244,7 @@
241244
"USE_VALUE",
242245
"UnsubscriptionError",
243246
"VIEW_REFS",
247+
"ViewContext",
244248
"ViewEncapsulation",
245249
"ViewRef",
246250
"ZONELESS_BY_DEFAULT",
@@ -297,6 +301,8 @@
297301
"addToArray",
298302
"addToEndOfViewTree",
299303
"addViewToDOM",
304+
"afterEveryRenderImpl",
305+
"afterNextRender",
300306
"allLeavingAnimations",
301307
"allocExpando",
302308
"allocLFrame",
@@ -450,6 +456,7 @@
450456
"getFirstLContainer",
451457
"getFirstNativeNode",
452458
"getGlobalLocale",
459+
"getHooks",
453460
"getInheritedInjectableDef",
454461
"getInitialLViewFlagsFromDef",
455462
"getInjectFlag",
@@ -532,6 +539,7 @@
532539
"injectElementRef",
533540
"injectInjectorOnly",
534541
"injectRootLimpMode",
542+
"injectViewContext",
535543
"injectableDefOrInjectorDefFactory",
536544
"insertBloom",
537545
"insertView",
@@ -681,8 +689,10 @@
681689
"runInInjectionContext",
682690
"runLeaveAnimationsWithCallback",
683691
"runPlatformInitializers",
692+
"runQueuedAnimations",
684693
"saveNameToExportMap",
685694
"saveResolvedLocalsInData",
695+
"scheduleAnimationQueue",
686696
"scheduleCallbackWithMicrotask",
687697
"scheduleCallbackWithRafRace",
688698
"searchTokensOnInjector",

packages/core/test/bundling/forms_reactive/bundle.golden_symbols.json

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,7 @@
11
{
22
"chunks": {
33
"main": [
4+
"AFTER_RENDER_PHASES",
45
"AFTER_RENDER_SEQUENCES_TO_ADD",
56
"ANIMATIONS",
67
"ANIMATION_QUEUE",
@@ -13,7 +14,9 @@
1314
"AbstractControlStatus",
1415
"AbstractFormDirective",
1516
"AbstractFormGroupDirective",
17+
"AfterRenderImpl",
1618
"AfterRenderManager",
19+
"AfterRenderSequence",
1720
"AnonymousSubject",
1821
"ApplicationInitStatus",
1922
"ApplicationModule",
@@ -267,6 +270,7 @@
267270
"Validators",
268271
"ValueChangeEvent",
269272
"ViewContainerRef",
273+
"ViewContext",
270274
"ViewEncapsulation",
271275
"ViewRef",
272276
"XSS_SECURITY_URL",
@@ -351,6 +355,8 @@
351355
"addToEndOfViewTree",
352356
"addValidators",
353357
"addViewToDOM",
358+
"afterEveryRenderImpl",
359+
"afterNextRender",
354360
"allLeavingAnimations",
355361
"allocExpando",
356362
"allocLFrame",
@@ -569,6 +575,7 @@
569575
"getFirstLContainer",
570576
"getFirstNativeNode",
571577
"getGlobalLocale",
578+
"getHooks",
572579
"getInheritedInjectableDef",
573580
"getInitialLViewFlagsFromDef",
574581
"getInjectFlag",
@@ -670,6 +677,7 @@
670677
"injectRootLimpMode",
671678
"injectTemplateRef",
672679
"injectViewContainerRef",
680+
"injectViewContext",
673681
"injectableDefOrInjectorDefFactory",
674682
"innerFrom",
675683
"insertAnchorNode",
@@ -905,8 +913,10 @@
905913
"runLeaveAnimationsWithCallback",
906914
"runPlatformInitializers",
907915
"runPostProducerCreatedFn",
916+
"runQueuedAnimations",
908917
"saveNameToExportMap",
909918
"saveResolvedLocalsInData",
919+
"scheduleAnimationQueue",
910920
"scheduleArray",
911921
"scheduleAsyncIterable",
912922
"scheduleCallbackWithMicrotask",

0 commit comments

Comments
 (0)