I'm trying to write a component that looks like on TV when sporting results will fly in row-by-row and build a result table. But now I have some Angular animations "side-effects".
My architecture looks like:
Scoreboard
-- View results component
-- View time keeping component
-- View xyz component
The scoreboard is the major compontent that receives data via a Websocket service. Depending on the received data a view component is dynamically created (createComponent(...)
) and shows the received data. That all works really well. Now I'm trying to add some nice animation effects, but this doesn't work as expected.
All data are shown at once (without any animation), when the data is received from the Websocket service.
I tried to reduce it to a an example shown below:
import { Component, OnInit } from "@angular/core";
import { trigger, transition, style, animate, query, stagger } from "@angular/animations";
@Component({
selector: "my-app",
templateUrl: "./app.component.html",
animations: [
trigger("listAnimation", [
transition("* => *", [
// each time the binding value changes
query(
":leave",
[stagger(100, [animate("0.5s", style({ opacity: 0 }))])],
{ optional: true }
),
query(
":enter",
[
style({ opacity: 0 }),
stagger(100, [animate("0.5s", style({ opacity: 1 }))])
],
{ optional: true }
)
])
])
],
})
export class AppComponent {
items = ["a", "b", "c"];
ngOnInit() {}
i = 0;
ngAfterViewInit() {
setInterval(() => {
this.items = this.i++ % 2 == 0 ? ["1", "2", "3"] : ["x", "y", "z"];
}, 3000);
}
}
The component is created and shows an initial animation, but when the array is changed in setInterval()
the view changes without any animation.
Why does it behave as it does? Or better: where is my mistake and how to solve?
Please see https://stackblitz.com/edit/angular-list-animations-dndsja?file=app%2Fapp.component.ts for a working example.
There are multiple issues.
The first one is that you are trying to animate <tr>
tags, I don't recommend to do this since the generated dom is different than the template.
So I switched to basic <div>
tags to fix this.
The second is that your array is never empty, that's why there is no leave animation.
So I set its value to an empty array for an instant using the setTimeout
function.
setInterval(() => {
this.items = [];
setTimeout(() => {
this.items = this.i++ % 2 == 0 ? ["1", "2", "3"] : ["x", "y", "z"];
}, 0);
}, 3000);
The third issue is that the animations are not synced, so I have to add a delay to the stagger animation in order to sync it.
I also did various tweaks to the animation to make it look good.
animations: [
trigger("listAnimation", [
transition("* => *", [
// each time the binding value changes
group([
query(
":leave",
[stagger(100, [animate("0.5s", style({ opacity: 0 }))])],
{ optional: true }
),
query(
":enter",
[
style({ opacity: 0, height: 0, visibility: 'hidden' }),
stagger(100, [
animate("0s 1.5s", style({})),
style({ height: "*", visibility: 'visible' }),
animate(".5s", style({ opacity: 1 }))
])
],
{ optional: true }
)
])
])
])
],
NB: there are still errors throwing in the console that I have yet to figure out.