Skip to content

Commit 7ec196e

Browse files
dario-piotrowiczdylhunn
authored andcommitted
fix(animations): make sure that the animation function delay is applied (#47285)
make sure that when an animation is defined via the `animation` function (and used via `useAnimation`) and a delay has been provided then that delay gets correctly applied resolves #47283 PR Close #47285
1 parent bba5abd commit 7ec196e

2 files changed

Lines changed: 161 additions & 1 deletion

File tree

packages/animations/browser/src/dsl/animation_timeline_builder.ts

Lines changed: 23 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -183,11 +183,34 @@ export class AnimationTimelineBuilderVisitor implements AstVisitor {
183183
visitAnimateRef(ast: AnimateRefAst, context: AnimationTimelineContext): any {
184184
const innerContext = context.createSubContext(ast.options);
185185
innerContext.transformIntoNewTimeline();
186+
this._applyAnimateRefDelay(ast.animation, context, innerContext);
186187
this.visitReference(ast.animation, innerContext);
187188
context.transformIntoNewTimeline(innerContext.currentTimeline.currentTime);
188189
context.previousNode = ast;
189190
}
190191

192+
private _applyAnimateRefDelay(
193+
animation: ReferenceAst, context: AnimationTimelineContext,
194+
innerContext: AnimationTimelineContext) {
195+
const animationDelay = animation.options?.delay;
196+
197+
if (!animationDelay) {
198+
return;
199+
}
200+
201+
let animationDelayValue: number;
202+
203+
if (typeof animationDelay === 'string') {
204+
const interpolatedDelay =
205+
interpolateParams(animationDelay, animation.options?.params ?? {}, context.errors);
206+
animationDelayValue = resolveTimingValue(interpolatedDelay);
207+
} else {
208+
animationDelayValue = animationDelay;
209+
}
210+
211+
innerContext.delayNextStep(animationDelayValue);
212+
}
213+
191214
private _visitSubInstructions(
192215
instructions: AnimationTimelineInstruction[], context: AnimationTimelineContext,
193216
options: AnimateChildOptions): number {

packages/core/test/animation/animation_integration_spec.ts

Lines changed: 138 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -5,7 +5,7 @@
55
* Use of this source code is governed by an MIT-style license that can be
66
* found in the LICENSE file at https://angular.io/license
77
*/
8-
import {animate, animateChild, AnimationEvent, AnimationMetadata, AnimationOptions, AUTO_STYLE, group, keyframes, query, state, style, transition, trigger, ɵPRE_STYLE as PRE_STYLE} from '@angular/animations';
8+
import {animate, animateChild, animation, AnimationEvent, AnimationMetadata, AnimationOptions, AUTO_STYLE, group, keyframes, query, state, style, transition, trigger, useAnimation, ɵPRE_STYLE as PRE_STYLE} from '@angular/animations';
99
import {AnimationDriver, ɵAnimationEngine, ɵNoopAnimationDriver as NoopAnimationDriver} from '@angular/animations/browser';
1010
import {MockAnimationDriver, MockAnimationPlayer} from '@angular/animations/browser/testing';
1111
import {ChangeDetectorRef, Component, HostBinding, HostListener, Inject, RendererFactory2, ViewChild} from '@angular/core';
@@ -3830,6 +3830,143 @@ describe('animation tests', function() {
38303830
/only state\(\) and transition\(\) definitions can sit inside of a trigger\(\)/);
38313831
});
38323832

3833+
describe('animation and useAnimation functions', () => {
3834+
it('should apply the delay specified in the animation', () => {
3835+
const animationMetaData = animation(
3836+
[
3837+
style({color: 'red'}),
3838+
animate(1000, style({color: 'green'})),
3839+
],
3840+
{delay: 3000});
3841+
3842+
@Component({
3843+
selector: 'cmp',
3844+
template: `
3845+
<div @anim *ngIf="exp">
3846+
</div>
3847+
`,
3848+
animations: [
3849+
trigger('anim', [transition(
3850+
':enter',
3851+
useAnimation(animationMetaData),
3852+
)]),
3853+
]
3854+
})
3855+
class Cmp {
3856+
exp: boolean = false;
3857+
}
3858+
3859+
TestBed.configureTestingModule({declarations: [Cmp]});
3860+
3861+
const engine = TestBed.inject(ɵAnimationEngine);
3862+
const fixture = TestBed.createComponent(Cmp);
3863+
const cmp = fixture.componentInstance;
3864+
cmp.exp = true;
3865+
3866+
fixture.detectChanges();
3867+
engine.flush();
3868+
3869+
const players = getLog();
3870+
expect(players.length).toEqual(1);
3871+
const [player] = players;
3872+
expect(player.delay).toEqual(3000);
3873+
expect(player.duration).toEqual(1000);
3874+
expect(player.keyframes).toEqual([
3875+
new Map<string, string|number>([['color', 'red'], ['offset', 0]]),
3876+
new Map<string, string|number>([['color', 'green'], ['offset', 1]]),
3877+
]);
3878+
});
3879+
3880+
it('should apply the delay specified in the animation using params', () => {
3881+
const animationMetaData = animation(
3882+
[
3883+
style({color: 'red'}),
3884+
animate(500, style({color: 'green'})),
3885+
],
3886+
{delay: '{{animationDelay}}ms', params: {animationDelay: 5500}});
3887+
3888+
@Component({
3889+
selector: 'cmp',
3890+
template: `
3891+
<div @anim *ngIf="exp">
3892+
</div>
3893+
`,
3894+
animations: [
3895+
trigger('anim', [transition(
3896+
':enter',
3897+
useAnimation(animationMetaData),
3898+
)]),
3899+
]
3900+
})
3901+
class Cmp {
3902+
exp: boolean = false;
3903+
}
3904+
3905+
TestBed.configureTestingModule({declarations: [Cmp]});
3906+
3907+
const engine = TestBed.inject(ɵAnimationEngine);
3908+
const fixture = TestBed.createComponent(Cmp);
3909+
const cmp = fixture.componentInstance;
3910+
cmp.exp = true;
3911+
3912+
fixture.detectChanges();
3913+
engine.flush();
3914+
3915+
const players = getLog();
3916+
expect(players.length).toEqual(1);
3917+
const [player] = players;
3918+
expect(player.delay).toEqual(5500);
3919+
expect(player.duration).toEqual(500);
3920+
expect(player.keyframes).toEqual([
3921+
new Map<string, string|number>([['color', 'red'], ['offset', 0]]),
3922+
new Map<string, string|number>([['color', 'green'], ['offset', 1]]),
3923+
]);
3924+
});
3925+
3926+
it('should combine the delay specified in the animation with that of the caller', () => {
3927+
const animationMetaData = animation(
3928+
[
3929+
style({color: 'red'}),
3930+
animate(500, style({color: 'green'})),
3931+
],
3932+
{delay: 2000});
3933+
3934+
@Component({
3935+
selector: 'cmp',
3936+
template: `
3937+
<div @anim *ngIf="exp">
3938+
</div>
3939+
`,
3940+
animations: [
3941+
trigger('anim', [transition(':enter', useAnimation(animationMetaData), {delay: 750})]),
3942+
]
3943+
})
3944+
class Cmp {
3945+
exp: boolean = false;
3946+
}
3947+
3948+
TestBed.configureTestingModule({declarations: [Cmp]});
3949+
3950+
const engine = TestBed.inject(ɵAnimationEngine);
3951+
const fixture = TestBed.createComponent(Cmp);
3952+
const cmp = fixture.componentInstance;
3953+
cmp.exp = true;
3954+
3955+
fixture.detectChanges();
3956+
engine.flush();
3957+
3958+
const players = getLog();
3959+
expect(players.length).toEqual(1);
3960+
const [player] = players;
3961+
expect(player.delay).toEqual(2750);
3962+
expect(player.duration).toEqual(500);
3963+
expect(player.keyframes).toEqual([
3964+
new Map<string, string|number>([['color', 'red'], ['offset', 0]]),
3965+
new Map<string, string|number>([['color', 'green'], ['offset', 1]]),
3966+
]);
3967+
});
3968+
});
3969+
38333970
it('should combine multiple errors together into one exception when an animation fails to be built',
38343971
() => {
38353972
@Component({

0 commit comments

Comments
 (0)