perf(animations): always run the animation queue outside of zones
Related #12732 Closes #13440
This commit is contained in:

committed by
Victor Berchet

parent
ecfad467a1
commit
e2622add07
@ -5,30 +5,47 @@
|
||||
* 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 {Injectable} from '../di/metadata';
|
||||
import {NgZone} from '../zone/ng_zone';
|
||||
import {AnimationPlayer} from './animation_player';
|
||||
|
||||
let _queuedAnimations: AnimationPlayer[] = [];
|
||||
@Injectable()
|
||||
export class AnimationQueue {
|
||||
public entries: AnimationPlayer[] = [];
|
||||
|
||||
/** @internal */
|
||||
export function queueAnimation(player: AnimationPlayer) {
|
||||
_queuedAnimations.push(player);
|
||||
}
|
||||
constructor(private _zone: NgZone) {}
|
||||
|
||||
/** @internal */
|
||||
export function triggerQueuedAnimations() {
|
||||
// this code is wrapped into a single promise such that the
|
||||
// onStart and onDone player callbacks are triggered outside
|
||||
// of the digest cycle of animations
|
||||
if (_queuedAnimations.length) {
|
||||
Promise.resolve(null).then(_triggerAnimations);
|
||||
enqueue(player: AnimationPlayer) { this.entries.push(player); }
|
||||
|
||||
flush() {
|
||||
// given that each animation player may set aside
|
||||
// microtasks and rely on DOM-based events, this
|
||||
// will cause Angular to run change detection after
|
||||
// each request. This sidesteps the issue. If a user
|
||||
// hooks into an animation via (@anim.start) or (@anim.done)
|
||||
// then those methods will automatically trigger change
|
||||
// detection by wrapping themselves inside of a zone
|
||||
if (this.entries.length) {
|
||||
this._zone.runOutsideAngular(() => {
|
||||
// this code is wrapped into a single promise such that the
|
||||
// onStart and onDone player callbacks are triggered outside
|
||||
// of the digest cycle of animations
|
||||
Promise.resolve(null).then(() => this._triggerAnimations());
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
private _triggerAnimations() {
|
||||
NgZone.assertNotInAngularZone();
|
||||
|
||||
while (this.entries.length) {
|
||||
const player = this.entries.shift();
|
||||
// in the event that an animation throws an error then we do
|
||||
// not want to re-run animations on any previous animations
|
||||
// if they have already been kicked off beforehand
|
||||
if (!player.hasStarted()) {
|
||||
player.play();
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
function _triggerAnimations() {
|
||||
for (let i = 0; i < _queuedAnimations.length; i++) {
|
||||
const player = _queuedAnimations[i];
|
||||
player.play();
|
||||
}
|
||||
_queuedAnimations = [];
|
||||
}
|
||||
|
@ -23,12 +23,14 @@ export class AnimationTransition {
|
||||
}
|
||||
|
||||
onStart(callback: (event: AnimationTransitionEvent) => any): void {
|
||||
const event = this._createEvent('start');
|
||||
this._player.onStart(() => callback(event));
|
||||
const fn =
|
||||
<() => void>Zone.current.wrap(() => callback(this._createEvent('start')), 'player.onStart');
|
||||
this._player.onStart(fn);
|
||||
}
|
||||
|
||||
onDone(callback: (event: AnimationTransitionEvent) => any): void {
|
||||
const event = this._createEvent('done');
|
||||
this._player.onDone(() => callback(event));
|
||||
const fn =
|
||||
<() => void>Zone.current.wrap(() => callback(this._createEvent('done')), 'player.onDone');
|
||||
this._player.onDone(fn);
|
||||
}
|
||||
}
|
||||
|
Reference in New Issue
Block a user