Pour fournir une expérience moderne et intuitive à vos utilisateurs, les animations sont un outil indispensable. Il y a plein de "moments" où une animation bien conçue facilite l'utilisation de votre application :
du feedback visuel pour valider une action de l'utilisateur ;
montrer un chargement ou une progression ;
attirer l'attention de l'utilisateur sur la prochaine étape d'une suite ;
lisser des transitions, ou des arrivées/départs d'éléments de l'écran.
Et encore bien d'autres !
Les animations CSS sont puissantes, mais ne peuvent pas tout faire – pas facilement, du moins ! Si, par exemple, une animation dépend d'une valeur dans votre component, ou si elle doit être déclenchée par un autre événement, ça devient de plus en plus difficile à gérer en CSS pur !
Les animations Angular sont un outil qui permet de répondre à ces besoins, comme vous le découvrirez au cours des prochains chapitres.
Commençons d'abord par créer une animation !
Ajoutez un trigger
Dans ce cours, BrowserAnimationsModule a été importé dans AppModule par Material. Profitons-en pour le déplacer dans les imports
de CoreModule (car il s'agit d'un module importé une seule fois pour toute l'application) :
import { NgModule } from '@angular/core';
import { CommonModule } from '@angular/common';
import { HeaderComponent } from './components/header/header.component';
import { SharedModule } from '../shared/shared.module';
import { RouterModule } from '@angular/router';
import { HttpClientModule } from '@angular/common/http';
import { BrowserAnimationsModule } from '@angular/platform-browser/animations';
@NgModule({
declarations: [
HeaderComponent
],
imports: [
CommonModule,
SharedModule,
RouterModule,
HttpClientModule,
BrowserAnimationsModule
],
exports: [
HeaderComponent
]
})
export class CoreModule { }
Je vous propose d'ajouter des animations aux commentaires des utilisateurs dans CommentsComponent.
Pour ajouter des animations dans un component, ça se passe dans l'objet de configuration que vous passez à son décorateur @Component
. Ajoutez un tableau animations
à CommentsComponent :
// ...
import { trigger } from '@angular/animations';
@Component({
selector: 'app-comments',
templateUrl: './comments.component.html',
styleUrls: ['./comments.component.scss'],
animations: [
trigger('listItem', [
])
]
})
export class CommentsComponent implements OnInit {
// ...
}
Le trigger
que vous ajoutez ici est ce qui permet d'attribuer une ou plusieurs animations aux éléments HTML dans votre template avec la syntaxe @
:
<!-- ... -->
<h2 matSubheader>Commentaires</h2>
<mat-list-item *ngFor="let comment of comments" @listItem>
<!-- ... -->
Bien sûr, dans l'application, il ne se passe rien encore, car vous n'avez pas encore ajouté d'animation ! Changeons ça tout de suite !
Définissez des états
Le plus souvent, les animations que vous ajoutez à vos applications sont des animations de transition entre différents états.
Ce sera le cas ici lorsque vous voudrez animer les commentaires des utilisateurs quand la souris passe dessus : vous animez la transition entre un état "par défaut" et un état "actif" ou "survolé".
Les animations Angular de ce type sont définies avec state()
, style()
, transition()
, et animate()
:
trigger('listItem', [
state('default', style({
transform: 'scale(1)',
'background-color': 'white',
'z-index': 1
})),
state('active', style({
transform: 'scale(1.05)',
'background-color': 'rgb(201, 157, 242)',
'z-index': 2
})),
transition('default => active', [
animate('100ms ease-in-out')
]),
transition('active => default', [
animate('500ms ease-in-out')
]),
])
Dans cet extrait :
vous définissez un état
default
comme état par défaut avecstate
, en lui passant le nom de l'état et un appel àstyle
. Àstyle
, vous passez les styles qui correspondent à cet état : il est essentiel de "réinitialiser" les styles qui seront modifiés lors du changement d'état ;vous définissez l'état lorsque la souris passera sur l'élément, avec le nom
active
et les styles correspondants ;vous configurez les transitions dans les deux sens en décrivant la
transition
à configurer et en y attribuant un tableau contenant la définition de l'animation avecanimate
– de default à active, ça ira beaucoup plus vite que de active à default.
Tout est prêt, mais votre trigger
ne sait pas encore quand ni comment passer d'un état à l'autre !
Changez d'état
Pour assigner un state
à un trigger
dans le template, vous allez utiliser l'attribute binding, avec une variable côté TypeScript qui contiendra soit 'default'
, soit 'active'
:
Dans le TypeScript :
listItemAnimationState: 'default' | 'active' = 'default';
Dans le template :
<mat-list-item *ngFor="let comment of comments" [@listItem]="listItemAnimationState">
Du coup, il suffit de modifier la valeur de listItemAnimationState
lorsque la souris entre et sort des MatListItem :
Dans le TypeScript :
onListItemMouseEnter() {
this.listItemAnimationState = 'active';
}
onListItemMouseLeave() {
this.listItemAnimationState = 'default';
}
Et dans le template :
<mat-list-item *ngFor="let comment of comments"
[@listItem]="listItemAnimationState"
(mouseenter)="onListItemMouseEnter()"
(mouseleave)="onListItemMouseLeave()">
On a un seul problème : l'animation a bien lieu, mais sur tous les commentaires à la fois !
Mais c'est normal ! On n'a pas créé de système permettant au component d'assigner des états différents aux différents MatListItem !
Faites la différence
À vous de jouer !
Je vous propose un challenge un peu costaud, pour lequel il y a énormément de solutions possibles !
Il faut trouver une façon de gérer les états différents de tous les commentaires dans chaque instance de CommentsComponent.
Chaque itération (c'est un indice !) de <mat-list-item>
devra pouvoir lire et modifier son état via l'attribute binding du trigger et les méthodes.
Il y a plein d'approches possibles : ci-dessous, je vous donne la mienne. Essayez par vous-même, puis suivez-moi !
Suivez ma solution
Je vous propose de créer un dictionnaire qui associe l'index du commentaire dans le tableau comments à son état :
animationStates: { [key: number]: 'default' | 'active' } = {};
Comme ça, le trigger
du template peut récupérer son état grâce à l' index
mis à disposition par *ngFor
:
<mat-list-item *ngFor="let comment of comments; let i = index"
[@listItem]="animationStates[i]">
Puisque cet objet est vide par défaut, il faut le peupler dans ngOnInit
:
for (let index in this.comments) {
this.animationStates[index] = 'default';
}
Pour chaque élément du tableau comments
, vous associez 'default'
à son index dans l'objet animationStates
. Par exemple, s'il y a trois commentaires, animationStates
finira comme ça :
animationStates = {
0: 'default',
1: 'default',
2: 'default'
};
Il ne reste plus qu'à modifier les empreintes et implémentations de vos deux méthodes d'événement pour qu'elles interagissent avec le dictionnaire :
onListItemMouseEnter(index: number) {
this.animationStates[index] = 'active';
}
onListItemMouseLeave(index: number) {
this.animationStates[index] = 'default';
}
Et leur passer l'index du commentaire depuis le template :
<mat-list-item *ngFor="let comment of comments; let i = index"
[@listItem]="animationStates[i]"
(mouseenter)="onListItemMouseEnter(i)"
(mouseleave)="onListItemMouseLeave(i)"
>
Avec ça, vos commentaires s'animent correctement !
En résumé
Les animations d'un component s'enregistrent au niveau du décorateur
@Component
.On utilise
trigger
pour définir un regroupement d'états et de transitions à assigner aux différents éléments.Les différents états d'un élément animé se définissent avec
state
et les styles passés àstyle
.Définir les transitions entre états se fait avec
transition
etanimate
.On assigne un trigger à un élément du template avec
@
, par exemple@fadeIn
.On peut assigner le state d'un trigger avec l'attribute binding, par exemple
[@fadeIn]="faded"
.
Dans le chapitre suivant, vous découvrirez un autre type de transition – vous êtes prêt ?