Skip to content
This repository was archived by the owner on Feb 22, 2022. It is now read-only.

Commit a9d5d45

Browse files
committed
feat: improve contract and add test
1 parent 4e2f687 commit a9d5d45

File tree

3 files changed

+38
-10
lines changed

3 files changed

+38
-10
lines changed

projects/ngx-css-anim/src/lib/animate.directive.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -85,7 +85,7 @@ export class AnimateDirective implements AfterViewInit {
8585
this.elementRef.nativeElement,
8686
animationToExecute,
8787
this.config.animationsDisabled
88-
).pipe(tap(() => this.animationEnd.emit()));
88+
).pipe(tap({ complete: () => this.animationEnd.emit() }));
8989
}
9090

9191
/**

projects/ngx-css-anim/src/lib/animation.spec.ts

Lines changed: 19 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -20,7 +20,7 @@ import { Subject } from 'rxjs';
2020
}
2121
2222
.animated {
23-
animation: animated 300ms ease;
23+
animation: animated 100ms ease;
2424
}
2525
`
2626
]
@@ -75,6 +75,22 @@ describe('animation', () => {
7575
expect(tester.div).toHaveClass('animated');
7676
});
7777

78+
it('should call onEnd, then emit, then complete', (done: DoneFn) => {
79+
let eventCount = 0;
80+
animate(tester.div.nativeElement, animation).subscribe({
81+
next: () => {
82+
expect(tester.div).not.toHaveClass('animated');
83+
eventCount++;
84+
},
85+
complete: () => {
86+
expect(eventCount).toBe(1);
87+
done();
88+
}
89+
});
90+
expect(tester.div).toHaveClass('animated');
91+
expect(eventCount).toBe(0);
92+
});
93+
7894
it('should animate by reacting to animationend event', () => {
7995
let eventCount = 0;
8096
let done = false;
@@ -106,7 +122,7 @@ describe('animation', () => {
106122
expect(eventCount).toBe(0);
107123
expect(done).toBe(false);
108124

109-
tick(350);
125+
tick(1250);
110126
tester.detectChanges();
111127

112128
expect(tester.div).not.toHaveClass('animated');
@@ -162,7 +178,7 @@ describe('animation', () => {
162178
tester.detectChanges();
163179
expect(tester.div).toHaveClass('animated');
164180

165-
tick(100);
181+
tick(50);
166182
interruption.next();
167183
interruption.complete();
168184

projects/ngx-css-anim/src/lib/animation.ts

Lines changed: 18 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -33,14 +33,17 @@ function getAnimationDurationInMillis(element: Element): number {
3333
* This `onStart` method is supposed to trigger a CSS animation (typically by adding a CSS class to the element, which defines
3434
* an animation). Then the duration of the animation is automatically obtained from the element. When the `animationend`
3535
* DOM event is emitted by the element or, by default (in case the event is never emitted), 20 milliseconds after the
36-
* scheduled end of the animation, the `onEnd` method of the animation, if any, is called. This `onEnd` method typically removes
37-
* the CSS class, so that the animation can be re-executed again later.
36+
* scheduled end of the animation, the `onEnd` method of the animation, if any, is called.
37+
* This `onEnd` method typically removes the CSS class, so that the animation can be re-executed again later.
38+
* Then, the observable emits and completes to signal the end of the animation.
39+
* If the returned observable is unsubscribed before it has emitted, the `onEnd` method is also called.
3840
* @param element the element to animate
3941
* @param animation the animation to apply on the element
4042
* @param synchronous if true, then the `onEnd` method of the animation is called synchronously, immediately
41-
* after the `onStart` method. This can be useful in tests, when you do not want the animation to take any time.
42-
* The returned observable, in that case, also emits and completes synchronously, as soon as it is subscribed.
43-
* @return an Observable which emits a single time and then completes, once the animation is done.
43+
* after the `onStart` method, and the returned observable also emits and completes synchronously.
44+
* This can be useful in tests, when you do not want the animation to take any time.
45+
* @return an Observable which emits a single time and then completes, once the animation is done and the `onEnd` method has been
46+
* called.
4447
*/
4548
export function animate(
4649
element: HTMLElement,
@@ -50,8 +53,16 @@ export function animate(
5053
return new Observable(observer => {
5154
animation.onStart(element);
5255
const onEnd = animation.onEnd ?? (() => {});
56+
let onEndCalled = false;
57+
const terminate = () => {
58+
if (!onEndCalled) {
59+
onEndCalled = true;
60+
onEnd(element);
61+
}
62+
};
5363
let subscription: Subscription | null = null;
5464
if (synchronous) {
65+
terminate();
5566
observer.next();
5667
observer.complete();
5768
} else {
@@ -64,14 +75,15 @@ export function animate(
6475
// emits once some time after the animation end, just in case the animationend event is not emitted
6576
const timeElapsed$ = timer(animationDuration + 20);
6677
subscription = race(animationEnd$, timeElapsed$).subscribe(() => {
78+
terminate();
6779
observer.next(undefined);
6880
observer.complete();
6981
});
7082
}
7183

7284
return () => {
7385
subscription?.unsubscribe();
74-
onEnd(element);
86+
terminate();
7587
};
7688
});
7789
}

0 commit comments

Comments
 (0)