Skip to content

Commit 3ab86bd

Browse files
matskochuckjaz
authored andcommitted
fix(animations): use a lightweight renderer for non-animation components (#17003)
1 parent 2538094 commit 3ab86bd

9 files changed

Lines changed: 277 additions & 87 deletions

File tree

‎packages/animations/browser/src/render/animation_engine_next.ts‎

Lines changed: 5 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -48,8 +48,11 @@ export class AnimationEngine {
4848
trigger = buildTrigger(name, ast);
4949
this._triggerCache[cacheKey] = trigger;
5050
}
51+
this._transitionEngine.registerTrigger(namespaceId, name, trigger);
52+
}
5153

52-
this._transitionEngine.register(namespaceId, hostElement, name, trigger);
54+
register(namespaceId: string, hostElement: any) {
55+
this._transitionEngine.register(namespaceId, hostElement);
5356
}
5457

5558
destroy(namespaceId: string, context: any) {
@@ -86,7 +89,7 @@ export class AnimationEngine {
8689
return this._transitionEngine.listen(namespaceId, element, eventName, eventPhase, callback);
8790
}
8891

89-
flush(countId: number = -1): void { this._transitionEngine.flush(countId); }
92+
flush(microtaskId: number = -1): void { this._transitionEngine.flush(microtaskId); }
9093

9194
get players(): AnimationPlayer[] {
9295
return (this._transitionEngine.players as AnimationPlayer[])

‎packages/animations/browser/src/render/transition_animation_engine.ts‎

Lines changed: 82 additions & 34 deletions
Original file line numberDiff line numberDiff line change
@@ -13,12 +13,13 @@ import {AnimationTransitionInstruction} from '../dsl/animation_transition_instru
1313
import {AnimationTrigger} from '../dsl/animation_trigger';
1414
import {ElementInstructionMap} from '../dsl/element_instruction_map';
1515
import {AnimationStyleNormalizer} from '../dsl/style_normalization/animation_style_normalizer';
16-
import {ENTER_CLASSNAME, LEAVE_CLASSNAME, LEAVE_SELECTOR, NG_ANIMATING_CLASSNAME, NG_TRIGGER_CLASSNAME, NG_TRIGGER_SELECTOR, copyObj, eraseStyles, iteratorToArray, setStyles} from '../util';
16+
import {ENTER_CLASSNAME, LEAVE_CLASSNAME, LEAVE_SELECTOR, NG_ANIMATING_CLASSNAME, NG_TRIGGER_CLASSNAME, NG_TRIGGER_SELECTOR, copyObj, eraseStyles, setStyles} from '../util';
1717

1818
import {AnimationDriver} from './animation_driver';
1919
import {getOrSetAsInMap, listenOnPlayer, makeAnimationEvent, normalizeKeyframes, optimizeGroupPlayer} from './shared';
2020

2121
const EMPTY_PLAYER_ARRAY: AnimationPlayer[] = [];
22+
const ANIMATE_EPOCH_ATTR = 'ng-animate-id';
2223

2324
interface TriggerListener {
2425
name: string;
@@ -255,7 +256,7 @@ export class AnimationTransitionNamespace {
255256
}
256257

257258
private _destroyInnerNodes(rootElement: any, context: any, animate: boolean = false) {
258-
listToArray(this._engine.driver.query(rootElement, NG_TRIGGER_SELECTOR, true)).forEach(elm => {
259+
this._engine.driver.query(rootElement, NG_TRIGGER_SELECTOR, true).forEach(elm => {
259260
if (animate && containsClass(elm, this._hostClassName)) {
260261
const innerNs = this._engine.namespacesByHostElement.get(elm);
261262

@@ -273,9 +274,7 @@ export class AnimationTransitionNamespace {
273274

274275
removeNode(element: any, context: any, doNotRecurse?: boolean): void {
275276
const engine = this._engine;
276-
277-
addClass(element, LEAVE_CLASSNAME);
278-
engine.afterFlush(() => removeClass(element, LEAVE_CLASSNAME));
277+
engine.markElementAsRemoved(element);
279278

280279
if (!doNotRecurse && element.childElementCount) {
281280
this._destroyInnerNodes(element, context, true);
@@ -382,7 +381,7 @@ export class AnimationTransitionNamespace {
382381

383382
insertNode(element: any, parent: any): void { addClass(element, this._hostClassName); }
384383

385-
drainQueuedTransitions(countId: number): QueueInstruction[] {
384+
drainQueuedTransitions(microtaskId: number): QueueInstruction[] {
386385
const instructions: QueueInstruction[] = [];
387386
this._queue.forEach(entry => {
388387
const player = entry.player;
@@ -395,7 +394,7 @@ export class AnimationTransitionNamespace {
395394
if (listener.name == entry.triggerName) {
396395
const baseEvent = makeAnimationEvent(
397396
element, entry.triggerName, entry.fromState.value, entry.toState.value);
398-
(baseEvent as any)['_data'] = countId;
397+
(baseEvent as any)['_data'] = microtaskId;
399398
listenOnPlayer(entry.player, listener.phase, baseEvent, listener.callback);
400399
}
401400
});
@@ -449,13 +448,13 @@ export interface QueuedTransition {
449448
export class TransitionAnimationEngine {
450449
public players: TransitionAnimationPlayer[] = [];
451450
public queuedRemovals = new Map<any, () => any>();
452-
public newlyInserted = new Set<any>();
453451
public newHostElements = new Map<any, AnimationTransitionNamespace>();
454452
public playersByElement = new Map<any, TransitionAnimationPlayer[]>();
455453
public playersByQueriedElement = new Map<any, TransitionAnimationPlayer[]>();
456454
public statesByElement = new Map<any, {[triggerName: string]: StateValue}>();
457455
public totalAnimations = 0;
458456
public totalQueuedPlayers = 0;
457+
public currentEpochId = 0;
459458

460459
private _namespaceLookup: {[id: string]: AnimationTransitionNamespace} = {};
461460
private _namespaceList: AnimationTransitionNamespace[] = [];
@@ -498,7 +497,7 @@ export class TransitionAnimationEngine {
498497
// animation renderer type. If this happens then we can still have
499498
// access to this item when we query for :enter nodes. If the parent
500499
// is a renderer then the set data-structure will normalize the entry
501-
this.newlyInserted.add(hostElement);
500+
this.updateElementEpoch(hostElement);
502501
}
503502
return this._namespaceLookup[namespaceId] = ns;
504503
}
@@ -526,17 +525,24 @@ export class TransitionAnimationEngine {
526525
return ns;
527526
}
528527

529-
register(namespaceId: string, hostElement: any, name: string, trigger: AnimationTrigger) {
528+
register(namespaceId: string, hostElement: any) {
530529
let ns = this._namespaceLookup[namespaceId];
531530
if (!ns) {
532531
ns = this.createNamespace(namespaceId, hostElement);
533532
}
534-
if (ns.register(name, trigger)) {
533+
return ns;
534+
}
535+
536+
registerTrigger(namespaceId: string, name: string, trigger: AnimationTrigger) {
537+
let ns = this._namespaceLookup[namespaceId];
538+
if (ns && ns.register(name, trigger)) {
535539
this.totalAnimations++;
536540
}
537541
}
538542

539543
destroy(namespaceId: string, context: any) {
544+
if (!namespaceId) return;
545+
540546
const ns = this._fetchNamespace(namespaceId);
541547

542548
this.afterFlush(() => {
@@ -564,20 +570,51 @@ export class TransitionAnimationEngine {
564570
insertNode(namespaceId: string, element: any, parent: any, insertBefore: boolean): void {
565571
if (!isElementNode(element)) return;
566572

567-
this._fetchNamespace(namespaceId).insertNode(element, parent);
573+
// special case for when an element is removed and reinserted (move operation)
574+
// when this occurs we do not want to use the element for deletion later
575+
if (this.queuedRemovals.has(element)) {
576+
this.markElementAsRemoved(element, true);
577+
this.queuedRemovals.delete(element);
578+
}
579+
580+
// in the event that the namespaceId is blank then the caller
581+
// code does not contain any animation code in it, but it is
582+
// just being called so that the node is marked as being inserted
583+
if (namespaceId) {
584+
this._fetchNamespace(namespaceId).insertNode(element, parent);
585+
}
568586

569587
// only *directives and host elements are inserted before
570588
if (insertBefore) {
571-
this.newlyInserted.add(element);
589+
this.updateElementEpoch(element);
590+
}
591+
}
592+
593+
updateElementEpoch(element: any, isRemoval?: boolean) {
594+
const epoch = (isRemoval ? -1 : 1) * this.currentEpochId;
595+
setAttribute(element, ANIMATE_EPOCH_ATTR, epoch);
596+
}
597+
598+
markElementAsRemoved(element: any, unmark?: boolean) {
599+
if (unmark) {
600+
removeClass(element, LEAVE_CLASSNAME);
601+
} else {
602+
addClass(element, LEAVE_CLASSNAME);
603+
this.afterFlush(() => removeClass(element, LEAVE_CLASSNAME));
572604
}
573605
}
574606

575607
removeNode(namespaceId: string, element: any, context: any, doNotRecurse?: boolean): void {
576-
const ns = this._fetchNamespace(namespaceId);
577-
if (!isElementNode(element) || !ns) {
578-
this._onRemovalComplete(element, context);
608+
if (namespaceId) {
609+
const ns = this._fetchNamespace(namespaceId);
610+
if (!isElementNode(element) || !ns) {
611+
this._onRemovalComplete(element, context);
612+
} else {
613+
ns.removeNode(element, context, doNotRecurse);
614+
}
579615
} else {
580-
ns.removeNode(element, context, doNotRecurse);
616+
this.markElementAsRemoved(element);
617+
this.queuedRemovals.set(element, () => this._onRemovalComplete(element, context));
581618
}
582619
}
583620

@@ -597,7 +634,7 @@ export class TransitionAnimationEngine {
597634
}
598635

599636
destroyInnerAnimations(containerElement: any) {
600-
listToArray(this.driver.query(containerElement, NG_TRIGGER_SELECTOR, true)).forEach(element => {
637+
this.driver.query(containerElement, NG_TRIGGER_SELECTOR, true).forEach(element => {
601638
const players = this.playersByElement.get(element);
602639
if (players) {
603640
players.forEach(player => {
@@ -628,20 +665,21 @@ export class TransitionAnimationEngine {
628665
});
629666
}
630667

631-
flush(countId: number = -1) {
668+
flush(microtaskId: number = -1) {
632669
let players: AnimationPlayer[] = [];
633670
if (this.newHostElements.size) {
634-
this.newHostElements.forEach((ns, element) => { this._balanceNamespaceList(ns, element); });
671+
this.newHostElements.forEach((ns, element) => this._balanceNamespaceList(ns, element));
635672
this.newHostElements.clear();
636673
}
637674

638675
if (this._namespaceList.length && (this.totalQueuedPlayers || this.queuedRemovals.size)) {
639-
players = this._flushAnimations(countId);
676+
players = this._flushAnimations(microtaskId);
677+
} else {
678+
this.queuedRemovals.forEach(fn => fn());
640679
}
641680

642681
this.totalQueuedPlayers = 0;
643682
this.queuedRemovals.clear();
644-
this.newlyInserted.clear();
645683
this._flushFns.forEach(fn => fn());
646684
this._flushFns = [];
647685

@@ -658,9 +696,11 @@ export class TransitionAnimationEngine {
658696
quietFns.forEach(fn => fn());
659697
}
660698
}
699+
700+
this.currentEpochId++;
661701
}
662702

663-
private _flushAnimations(countId: number): TransitionAnimationPlayer[] {
703+
private _flushAnimations(microtaskId: number): TransitionAnimationPlayer[] {
664704
const subTimelines = new ElementInstructionMap();
665705
const skippedPlayers: TransitionAnimationPlayer[] = [];
666706
const skippedPlayersMap = new Map<any, AnimationPlayer[]>();
@@ -672,13 +712,15 @@ export class TransitionAnimationEngine {
672712
// this must occur before the instructions are built below such that
673713
// the :enter queries match the elements (since the timeline queries
674714
// are fired during instruction building).
675-
const allEnterNodes = iteratorToArray(this.newlyInserted.values());
676-
const enterNodes: any[] = collectEnterElements(this.driver, allEnterNodes);
677715
const bodyNode = getBodyNode();
716+
const allEnterNodes: any[] =
717+
bodyNode ? this.driver.query(bodyNode, makeEpochSelector(this.currentEpochId), true) : [];
718+
const enterNodes: any[] =
719+
allEnterNodes.length ? collectEnterElements(this.driver, allEnterNodes) : [];
678720

679721
for (let i = this._namespaceList.length - 1; i >= 0; i--) {
680722
const ns = this._namespaceList[i];
681-
ns.drainQueuedTransitions(countId).forEach(entry => {
723+
ns.drainQueuedTransitions(microtaskId).forEach(entry => {
682724
const player = entry.player;
683725

684726
const element = entry.element;
@@ -751,7 +793,7 @@ export class TransitionAnimationEngine {
751793
allPreviousPlayersMap.forEach(players => players.forEach(player => player.destroy()));
752794

753795
const leaveNodes: any[] = bodyNode && allPostStyleElements.size ?
754-
listToArray(this.driver.query(bodyNode, LEAVE_SELECTOR, true)) :
796+
this.driver.query(bodyNode, LEAVE_SELECTOR, true) :
755797
[];
756798

757799
// PRE STAGE: fill the ! styles
@@ -866,7 +908,6 @@ export class TransitionAnimationEngine {
866908
elementContainsData(namespaceId: string, element: any) {
867909
let containsData = false;
868910
if (this.queuedRemovals.has(element)) containsData = true;
869-
if (this.newlyInserted.has(element)) containsData = true;
870911
if (this.playersByElement.has(element)) containsData = true;
871912
if (this.playersByQueriedElement.has(element)) containsData = true;
872913
if (this.statesByElement.has(element)) containsData = true;
@@ -1217,12 +1258,6 @@ function cloakAndComputeStyles(
12171258
return valuesMap;
12181259
}
12191260

1220-
function listToArray(list: any): any[] {
1221-
const arr: any[] = [];
1222-
arr.push(...(list as any[]));
1223-
return arr;
1224-
}
1225-
12261261
function collectEnterElements(driver: AnimationDriver, allEnterNodes: any[]) {
12271262
allEnterNodes.forEach(element => addClass(element, POTENTIAL_ENTER_CLASSNAME));
12281263
const enterNodes = filterNodeClasses(driver, getBodyNode(), POTENTIAL_ENTER_SELECTOR);
@@ -1264,9 +1299,22 @@ function removeClass(element: any, className: string) {
12641299
}
12651300
}
12661301

1302+
function setAttribute(element: any, attr: string, value: any) {
1303+
if (element.setAttribute) {
1304+
element.setAttribute(attr, value);
1305+
} else {
1306+
element[attr] = value;
1307+
}
1308+
}
1309+
12671310
function getBodyNode(): any|null {
12681311
if (typeof document != 'undefined') {
12691312
return document.body;
12701313
}
12711314
return null;
12721315
}
1316+
1317+
function makeEpochSelector(epochId: number, isRemoval?: boolean) {
1318+
const value = (isRemoval ? -1 : 1) * epochId;
1319+
return `[${ANIMATE_EPOCH_ATTR}="${value}"]`;
1320+
}

‎packages/animations/browser/test/render/transition_animation_engine_spec.ts‎

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -660,7 +660,8 @@ function registerTrigger(
660660
if (errors.length) {
661661
}
662662
const trigger = buildTrigger(name, ast);
663-
engine.register(id, element, name, trigger)
663+
engine.register(id, element);
664+
engine.registerTrigger(id, name, trigger);
664665
}
665666

666667
function setProperty(

‎packages/core/test/animation/animation_integration_spec.ts‎

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1101,9 +1101,10 @@ export function main() {
11011101
const cmp = fixture.componentInstance;
11021102

11031103
const someTrigger = trigger('someTrigger', []);
1104+
const hostElement = fixture.nativeElement;
1105+
engine.register(DEFAULT_NAMESPACE_ID, hostElement);
11041106
engine.registerTrigger(
1105-
DEFAULT_COMPONENT_ID, DEFAULT_NAMESPACE_ID, fixture.nativeElement, someTrigger.name,
1106-
someTrigger);
1107+
DEFAULT_COMPONENT_ID, DEFAULT_NAMESPACE_ID, hostElement, someTrigger.name, someTrigger);
11071108

11081109
cmp.exp = 'a';
11091110
fixture.detectChanges();

0 commit comments

Comments
 (0)