Skip to content

Commit

Permalink
fix(animations): make sure that the useAnimation function delay is ap…
Browse files Browse the repository at this point in the history
…plied

make sure that when an animation is defined via the `useAnimation` function
and a delay has been provided then that delay gets correctly applied

(this PR is a follow up for angular#47285)
  • Loading branch information
dario-piotrowicz committed Sep 17, 2022
1 parent 1f88df0 commit 061ff6c
Show file tree
Hide file tree
Showing 2 changed files with 114 additions and 25 deletions.
31 changes: 12 additions & 19 deletions packages/animations/browser/src/dsl/animation_timeline_builder.ts
Original file line number Diff line number Diff line change
Expand Up @@ -183,32 +183,25 @@ export class AnimationTimelineBuilderVisitor implements AstVisitor {
visitAnimateRef(ast: AnimateRefAst, context: AnimationTimelineContext): any {
const innerContext = context.createSubContext(ast.options);
innerContext.transformIntoNewTimeline();
this._applyAnimateRefDelay(ast.animation, context, innerContext);
this._applyAnimationRefDelays([ast.options, ast.animation.options], context, innerContext);
this.visitReference(ast.animation, innerContext);
context.transformIntoNewTimeline(innerContext.currentTimeline.currentTime);
context.previousNode = ast;
}

private _applyAnimateRefDelay(
animation: ReferenceAst, context: AnimationTimelineContext,
private _applyAnimationRefDelays(
animationsRefsOptions: (AnimationOptions|null)[], context: AnimationTimelineContext,
innerContext: AnimationTimelineContext) {
const animationDelay = animation.options?.delay;

if (!animationDelay) {
return;
}

let animationDelayValue: number;

if (typeof animationDelay === 'string') {
const interpolatedDelay =
interpolateParams(animationDelay, animation.options?.params ?? {}, context.errors);
animationDelayValue = resolveTimingValue(interpolatedDelay);
} else {
animationDelayValue = animationDelay;
for (const animationRefOptions of animationsRefsOptions) {
const animationDelay = animationRefOptions?.delay;
if (animationDelay) {
const animationDelayValue = typeof animationDelay === 'number' ?
animationDelay :
resolveTimingValue(interpolateParams(
animationDelay, animationRefOptions?.params ?? {}, context.errors));
innerContext.delayNextStep(animationDelayValue);
}
}

innerContext.delayNextStep(animationDelayValue);
}

private _visitSubInstructions(
Expand Down
108 changes: 102 additions & 6 deletions packages/core/test/animation/animation_integration_spec.ts
Original file line number Diff line number Diff line change
Expand Up @@ -3923,13 +3923,57 @@ describe('animation tests', function() {
]);
});

it('should combine the delay specified in the animation with that of the caller', () => {
it('should apply the delay specified in the useAnimation call', () => {
const animationMetaData = animation([
style({color: 'red'}),
animate(550, style({color: 'green'})),
]);

@Component({
selector: 'cmp',
template: `
<div @anim *ngIf="exp">
</div>
`,
animations: [
trigger('anim', [transition(
':enter',
useAnimation(animationMetaData, {delay: 1500}),
)]),
]
})
class Cmp {
exp: boolean = false;
}

TestBed.configureTestingModule({declarations: [Cmp]});

const engine = TestBed.inject(ɵAnimationEngine);
const fixture = TestBed.createComponent(Cmp);
const cmp = fixture.componentInstance;
cmp.exp = true;

fixture.detectChanges();
engine.flush();

const players = getLog();
expect(players.length).toEqual(1);
const [player] = players;
expect(player.delay).toEqual(1500);
expect(player.duration).toEqual(550);
expect(player.keyframes).toEqual([
new Map<string, string|number>([['color', 'red'], ['offset', 0]]),
new Map<string, string|number>([['color', 'green'], ['offset', 1]]),
]);
});

it('should apply the delay specified in the useAnimation call using params', () => {
const animationMetaData = animation(
[
style({color: 'red'}),
animate(500, style({color: 'green'})),
animate(700, style({color: 'green'})),
],
{delay: 2000});
);

@Component({
selector: 'cmp',
Expand All @@ -3938,7 +3982,13 @@ describe('animation tests', function() {
</div>
`,
animations: [
trigger('anim', [transition(':enter', useAnimation(animationMetaData), {delay: 750})]),
trigger('anim', [transition(
':enter',
useAnimation(animationMetaData, {
delay: '{{useAnimationDelay}}ms',
params: {useAnimationDelay: 7500}
}),
)]),
]
})
class Cmp {
Expand All @@ -3958,13 +4008,59 @@ describe('animation tests', function() {
const players = getLog();
expect(players.length).toEqual(1);
const [player] = players;
expect(player.delay).toEqual(2750);
expect(player.duration).toEqual(500);
expect(player.delay).toEqual(7500);
expect(player.duration).toEqual(700);
expect(player.keyframes).toEqual([
new Map<string, string|number>([['color', 'red'], ['offset', 0]]),
new Map<string, string|number>([['color', 'green'], ['offset', 1]]),
]);
});

it('should combine the delays specified in the animation and the useAnimation with that of the caller',
() => {
const animationMetaData = animation(
[
style({color: 'red'}),
animate(567, style({color: 'green'})),
],
{delay: 1000});

@Component({
selector: 'cmp',
template: `
<div @anim *ngIf="exp">
</div>
`,
animations: [
trigger('anim', [transition(
':enter', useAnimation(animationMetaData, {delay: 34}),
{delay: 200})]),
]
})
class Cmp {
exp: boolean = false;
}

TestBed.configureTestingModule({declarations: [Cmp]});

const engine = TestBed.inject(ɵAnimationEngine);
const fixture = TestBed.createComponent(Cmp);
const cmp = fixture.componentInstance;
cmp.exp = true;

fixture.detectChanges();
engine.flush();

const players = getLog();
expect(players.length).toEqual(1);
const [player] = players;
expect(player.delay).toEqual(1234);
expect(player.duration).toEqual(567);
expect(player.keyframes).toEqual([
new Map<string, string|number>([['color', 'red'], ['offset', 0]]),
new Map<string, string|number>([['color', 'green'], ['offset', 1]]),
]);
});
});

it('should combine multiple errors together into one exception when an animation fails to be built',
Expand Down

0 comments on commit 061ff6c

Please sign in to comment.