perf(animations): always run the animation queue outside of zones

Related #12732
Closes #13440
This commit is contained in:
Matias Niemelä
2016-12-09 13:04:18 -08:00
committed by Victor Berchet
parent ecfad467a1
commit e2622add07
17 changed files with 441 additions and 106 deletions

View File

@ -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 = [];
}

View File

@ -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);
}
}