fix(animations): properly cleanup query artificats when animation construction fails
This commit is contained in:

committed by
Jason Aden

parent
71ee0c5b03
commit
858dea98e5
@ -29,12 +29,16 @@ export class AnimationTransitionFactory {
|
||||
build(
|
||||
driver: AnimationDriver, element: any, currentState: any, nextState: any,
|
||||
options?: AnimationOptions,
|
||||
subInstructions?: ElementInstructionMap): AnimationTransitionInstruction|undefined {
|
||||
subInstructions?: ElementInstructionMap): AnimationTransitionInstruction {
|
||||
const animationOptions = mergeAnimationOptions(this.ast.options || {}, options || {});
|
||||
|
||||
const backupStateStyles = this._stateStyles['*'] || {};
|
||||
const currentStateStyles = this._stateStyles[currentState] || backupStateStyles;
|
||||
const nextStateStyles = this._stateStyles[nextState] || backupStateStyles;
|
||||
const queriedElements = new Set<any>();
|
||||
const preStyleMap = new Map<any, {[prop: string]: boolean}>();
|
||||
const postStyleMap = new Map<any, {[prop: string]: boolean}>();
|
||||
const isRemoval = nextState === 'void';
|
||||
|
||||
const errors: any[] = [];
|
||||
const timelines = buildAnimationTimelines(
|
||||
@ -42,13 +46,11 @@ export class AnimationTransitionFactory {
|
||||
subInstructions, errors);
|
||||
|
||||
if (errors.length) {
|
||||
const errorMessage = `animation building failed:\n${errors.join("\n")}`;
|
||||
throw new Error(errorMessage);
|
||||
return createTransitionInstruction(
|
||||
element, this._triggerName, currentState, nextState, isRemoval, currentStateStyles,
|
||||
nextStateStyles, [], [], preStyleMap, postStyleMap, errors);
|
||||
}
|
||||
|
||||
const preStyleMap = new Map<any, {[prop: string]: boolean}>();
|
||||
const postStyleMap = new Map<any, {[prop: string]: boolean}>();
|
||||
const queriedElements = new Set<any>();
|
||||
timelines.forEach(tl => {
|
||||
const elm = tl.element;
|
||||
const preProps = getOrSetAsInMap(preStyleMap, elm, {});
|
||||
@ -64,9 +66,8 @@ export class AnimationTransitionFactory {
|
||||
|
||||
const queriedElementsList = iteratorToArray(queriedElements.values());
|
||||
return createTransitionInstruction(
|
||||
element, this._triggerName, currentState, nextState, nextState === 'void',
|
||||
currentStateStyles, nextStateStyles, timelines, queriedElementsList, preStyleMap,
|
||||
postStyleMap);
|
||||
element, this._triggerName, currentState, nextState, isRemoval, currentStateStyles,
|
||||
nextStateStyles, timelines, queriedElementsList, preStyleMap, postStyleMap);
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -21,6 +21,7 @@ export interface AnimationTransitionInstruction extends AnimationEngineInstructi
|
||||
queriedElements: any[];
|
||||
preStyleProps: Map<any, {[prop: string]: boolean}>;
|
||||
postStyleProps: Map<any, {[prop: string]: boolean}>;
|
||||
errors?: any[];
|
||||
}
|
||||
|
||||
export function createTransitionInstruction(
|
||||
@ -28,7 +29,8 @@ export function createTransitionInstruction(
|
||||
isRemovalTransition: boolean, fromStyles: ɵStyleData, toStyles: ɵStyleData,
|
||||
timelines: AnimationTimelineInstruction[], queriedElements: any[],
|
||||
preStyleProps: Map<any, {[prop: string]: boolean}>,
|
||||
postStyleProps: Map<any, {[prop: string]: boolean}>): AnimationTransitionInstruction {
|
||||
postStyleProps: Map<any, {[prop: string]: boolean}>,
|
||||
errors?: any[]): AnimationTransitionInstruction {
|
||||
return {
|
||||
type: AnimationTransitionInstructionType.TransitionAnimation,
|
||||
element,
|
||||
@ -41,6 +43,7 @@ export function createTransitionInstruction(
|
||||
timelines,
|
||||
queriedElements,
|
||||
preStyleProps,
|
||||
postStyleProps
|
||||
postStyleProps,
|
||||
errors
|
||||
};
|
||||
}
|
||||
|
@ -745,6 +745,7 @@ export class TransitionAnimationEngine {
|
||||
const queriedElements = new Map<any, TransitionAnimationPlayer[]>();
|
||||
const allPreStyleElements = new Map<any, Set<string>>();
|
||||
const allPostStyleElements = new Map<any, Set<string>>();
|
||||
const cleanupFns: Function[] = [];
|
||||
|
||||
const bodyNode = getBodyNode();
|
||||
const allEnterNodes: any[] = this.collectedEnterElements.length ?
|
||||
@ -772,10 +773,21 @@ export class TransitionAnimationEngine {
|
||||
}
|
||||
}
|
||||
|
||||
cleanupFns.push(() => {
|
||||
allEnterNodes.forEach(element => removeClass(element, ENTER_CLASSNAME));
|
||||
allLeaveNodes.forEach(element => {
|
||||
removeClass(element, LEAVE_CLASSNAME);
|
||||
this.processLeaveNode(element);
|
||||
});
|
||||
});
|
||||
|
||||
const allPlayers: TransitionAnimationPlayer[] = [];
|
||||
const erroneousTransitions: AnimationTransitionInstruction[] = [];
|
||||
for (let i = this._namespaceList.length - 1; i >= 0; i--) {
|
||||
const ns = this._namespaceList[i];
|
||||
ns.drainQueuedTransitions(microtaskId).forEach(entry => {
|
||||
const player = entry.player;
|
||||
allPlayers.push(player);
|
||||
|
||||
const element = entry.element;
|
||||
if (!bodyNode || !this.driver.containsElement(bodyNode, element)) {
|
||||
@ -783,8 +795,11 @@ export class TransitionAnimationEngine {
|
||||
return;
|
||||
}
|
||||
|
||||
const instruction = this._buildInstruction(entry, subTimelines);
|
||||
if (!instruction) return;
|
||||
const instruction = this._buildInstruction(entry, subTimelines) !;
|
||||
if (instruction.errors && instruction.errors.length) {
|
||||
erroneousTransitions.push(instruction);
|
||||
return;
|
||||
}
|
||||
|
||||
// if a unmatched transition is queued to go then it SHOULD NOT render
|
||||
// an animation and cancel the previously running animations.
|
||||
@ -833,6 +848,18 @@ export class TransitionAnimationEngine {
|
||||
});
|
||||
}
|
||||
|
||||
if (erroneousTransitions.length) {
|
||||
let msg = `Unable to process animations due to the following failed trigger transitions\n`;
|
||||
erroneousTransitions.forEach(instruction => {
|
||||
msg += `@${instruction.triggerName} has failed due to:\n`;
|
||||
instruction.errors !.forEach(error => { msg += `- ${error}\n`; });
|
||||
});
|
||||
|
||||
cleanupFns.forEach(fn => fn());
|
||||
allPlayers.forEach(player => player.destroy());
|
||||
throw new Error(msg);
|
||||
}
|
||||
|
||||
// these can only be detected here since we have a map of all the elements
|
||||
// that have animations attached to them...
|
||||
const enterNodesWithoutAnimations: any[] = [];
|
||||
@ -937,6 +964,7 @@ export class TransitionAnimationEngine {
|
||||
for (let i = 0; i < allLeaveNodes.length; i++) {
|
||||
const element = allLeaveNodes[i];
|
||||
const details = element[REMOVAL_FLAG] as ElementAnimationState;
|
||||
removeClass(element, LEAVE_CLASSNAME);
|
||||
|
||||
// this means the element has a removal animation that is being
|
||||
// taken care of and therefore the inner elements will hang around
|
||||
@ -969,6 +997,9 @@ export class TransitionAnimationEngine {
|
||||
}
|
||||
}
|
||||
|
||||
// this is required so the cleanup method doesn't remove them
|
||||
allLeaveNodes.length = 0;
|
||||
|
||||
rootPlayers.forEach(player => {
|
||||
this.players.push(player);
|
||||
player.onDone(() => {
|
||||
@ -980,8 +1011,7 @@ export class TransitionAnimationEngine {
|
||||
player.play();
|
||||
});
|
||||
|
||||
allEnterNodes.forEach(element => removeClass(element, ENTER_CLASSNAME));
|
||||
|
||||
cleanupFns.forEach(fn => fn());
|
||||
return rootPlayers;
|
||||
}
|
||||
|
||||
|
Reference in New Issue
Block a user