Angular, fra le tante cose, include un complesso sistema di animazioni. Ma lo sapevate che potete usare AnimationBuilder per scatenare animazioni a piacimento, in modo programmatico? No? Vediamolo assieme!

Creiamo una lista

Come prima cosa, creiamo una lista da mostrare in un ngFor. Facciamo gli sviluppatori seri e tipizziamo questa lista con un’interfaccia! 😀

import { Component, OnInit } from '@angular/core';

interface User {
  id: number;
  name: string;
}

@Component({
  selector: 'my-app',
  template: `
    <ul>
      <li>{{ user.name }}</li>
    </ul>
  `
})
export class AppComponent implements OnInit {

  users: User[] = [];

  ngOnInit() {
    this.users = [
      { id: Math.random(), name: 'Michele' }
    ]
  }
}

Come vedete, inizializziamo la nostra lista di utenti con un array vuoto, e all’OnInit la valorizziamo. Non solo: stiamo trattando la lista come un oggetto immutabile. In poche parole, non la modifichiamo con un push, quando il valore cambia la sovrascriviamo completamente. Vedremo fra poco perché ho fatto questa scelta!

Come dite? A cosa serve quell’id? Questo ve lo dico subito.

Animiamo la lista

Per prima cosa, ci serve un metodo per aggiungere utenti alla lista. Vogliamo animarla, no?

addUser() {
  this.users = [
    ...this.users.map(user => ({ ...user })),
    { id: Math.random(), name: 'New user' }
  ];
}

Anche qui, approccio immutabile. Non solo: sto usando lo spread operator per rimpiazzare completamente i vecchi utenti con dei nuovi utenti! Identici, ma diversi, infatti il loro puntatore in memoria cambierà. Curiosi eh?

Ora pensiamo alle animazioni: vogliamo che ogni nuovo utente appaia con un’animazione. Scriviamone una semplice.

@Component({
  selector: 'my-app',
  template: `
    <button (click)="addUser()">Add user</button>
    <ul>
      <li *ngFor="let user of users" @fadeIn>{{ user.name }}</li>
    </ul>
  `,
  animations: [
    trigger('fadeIn', [
      transition(':enter', [
        style({ transform: 'scale(0.5)', opacity: 0 }),
        animate(
          '.3s cubic-bezier(.8, -0.6, 0.2, 1.5)', 
          style({ transform: 'scale(1)', opacity: 1 })
        )
      ])
    ])
  ]
})
export class AppComponent implements OnInit {

  users: User[] = [];

  ngOnInit() {
    this.users = [
      { id: Math.random(), name: 'Michele' }
    ]
  }

  addUser() {
    this.users = [
      ...this.users.map(user => ({ ...user })),
      { id: Math.random(), name: 'New user' }
    ];
  }
}

Facciamo partire il codice e… non funziona! O meglio, funziona a metà: tutta la lista viene ri-animata quando aggiungiamo un elemento, anche se abbiamo applicato il nostro trigger direttamente sull’elemento <li>. Non è proprio quello che volevamo, no? Noi vogliamo che solamente i nuovi elementi appaiano con un’animazione. Ecco perché ho usato la proprietà id e perché ho adottato un approccio immutabile: per spiegarvi trackBy.

Ottimizziamo la lista con trackBy e aggiustiamo l’animazione

Si tratta di uno strumento utilissimo che Angular ci permette di utilizzare in abbinata con ngFor. Infatti, grazie a trackBy, possiamo dire ad Angular qual è la proprietà che contraddistingue ogni elemento, in modo che, quando gli elementi cambieranno, Angular non dovrà ri-renderizzare gli elementi già presenti.

@Component({
  selector: 'my-app',
  template: `
    <button (click)="addUser()">Add user</button>
    <ul>
      <li
        *ngFor="let user of users; trackBy: trackByUserId"
        @fadeIn
      >{{ user.name }}</li>
    </ul>
  `,
  animations: [
    trigger('fadeIn', [
      transition(':enter', [
        style({ transform: 'scale(0.5)', opacity: 0 }),
        animate(
          '.3s cubic-bezier(.8, -0.6, 0.2, 1.5)', 
          style({ transform: 'scale(1)', opacity: 1 })
        )
      ])
    ])
  ]
})
export class AppComponent implements OnInit {

  users: User[] = [];

  ngOnInit() {
    this.users = [
      { id: Math.random(), name: 'Michele' }
    ]
  }

  addUser() {
    this.users = [
      ...this.users.map(user => ({ ...user })),
      { id: Math.random(), name: 'New user' }
    ];
  }

  trackByUserId(index, user: User) {
    return user.id;
  }
}

Ok, ora ci siamo! TrackBy non solamente ci ha aiutato in fatto di performance, ma ha anche permesso alla nostra transizione :enter di funzionare correttamente, perché i vecchi elementi non vengono rigenerati, e quindi non vengono presi di mira dall’animazione.

Ho utilizzato un approccio immutabile proprio per fare questa dimostrazione. Anche se gli elementi cambiano, Angular ricorderà il loro id e non li renderizzerà nuovamente.

Ora arriva il bello: immaginate che all’interno del <li> ci sia un form con le informazioni dell’utente. Il visitatore fa delle modifiche, preme il tasto salva e… quel form viene animato! Sarebbe figo, no? Ci sono più modi di ottenere questo effetto, ma oggi vediamo come ottenerlo con AnimationBuilder.

Utilizziamo AnimationBuilder per animare gli elementi

Anche qui, comportiamoci bene e scriviamo una direttiva:

import { Directive, ElementRef } from '@angular/core';
import { AnimationBuilder, style, animate } from '@angular/animations';

@Directive({
  selector: '[blink]',
  exportAs: 'blink'
})
export class BlinkDirective {

  constructor(
    private animationBuilder: AnimationBuilder,
    private el: ElementRef
  ) {}
}

Questa direttiva sarà la responsabile dell’animazione sul singolo utente: l’ho chiamata BlinkDirective perché l’animazione sarà una specie di lampeggio. Più o meno.

Come vedete, ho iniettato il servizio AnimationBuilder e ElementRef, che contiene il riferimento all’elemento sul quale è applicata la direttiva. Ho anche esportato la direttiva con exportAs, una parte fondamentale del nostro piano. Se non l’avete mai usata, scoprirete fra poco a cosa serve.

Ricordate di includere BlinkDirective nell'NgModule giusto!

Ora, dobbiamo scrivere la nostra animazione. Ma come la scriviamo? E come la lanciamo? Beh, per entrambi utilizzeremo AnimationBuilder!

@Directive({
  selector: '[blink]',
  exportAs: 'blink'
})
export class BlinkDirective {

  constructor(
    private animationBuilder: AnimationBuilder,
    private el: ElementRef
  ) {}

  start() {
    // La nostra animazione!
    const myAnimation = this.animationBuilder.build([
      style({ transform: 'scale(1)', opacity: 1 }),
      animate(150, style({ transform: 'scale(1.1)', opacity: .5 })),
      animate(150, style({ transform: 'scale(1)', opacity: 1 }))
    ]);
 
    const player = myAnimation.create(this.el.nativeElement);
    // Facciamo partire l'animazione
    player.play();
  }
}

Sembra complicato, ma non lo è: all’interno del metodo build scriviamo la nostra animazione, esattamente come la scriviamo nelle transition che scriviamo nei metadati di un componente. In questo caso, si partirà da uno stile di base, poi l’oggetto verrà scalato e ridotta l’opacità, per poi ritornare normale. Il metodo build ci ritorna una AnimationFactory, una specie di “istruzione” su come effettuare una animazione. Dobbiamo però agganciarla ad un elemento del DOM.

Quindi, abbiamo creato un AnimationPlayer con il metodo create: questo player è il legame fra l’animazione e il nostro elemento (un po’ come una Subscription di RxJS!) e possiamo utilizzarlo per far partire, mettere in pausa, far ripartire o stoppare un’animazione.

Infine, chiamiamo il metodo play per far partire l’animazione. Non ci interessano altre funzionalità per il momento, ma se volete vedere ciò di cui è capace l’AnimationPlayer, ecco la documentazione!

Applichiamo la direttiva alla lista

Ora non ci resta che utilizzare questa direttiva: vi ricorderete che ho utilizzato la proprietà exportAs nei metadati della direttiva, giusto? Ecco a cosa ci serve: ad accedervi dall’esterno!

@Component({
  selector: 'my-app',
  template: `
    <button (click)="addUser()">Add user</button>
    <ul>
      <li
        *ngFor="let user of users; trackBy: trackByUserId"
        @fadeIn
        blink
        #blinkDir="blink"
      >{{ user.name }} <button (click)="blinkDir.start()">Make me blink</button></li>
    </ul>
  `,
  animations: [ ... ]
})
export class AppComponent implements OnInit {

  ...
}

Ho applicato la direttiva blink ad ogni elemento della lista, l’ho messa in una variabile di template (quella con il cancelletto!) grazie a exportAs, e ho creato un button che, premuto, chiama il metodo start sulla nostra direttiva per far partire l’animazione. Ora la nostra lista è veramente animata!

Conclusione

A questo punto avrete capito che potete utilizzare AnimationBuilder un po’ come vi pare, perché potete far partire le animazioni direttamente dal vostro codice JavaScript: i casi d’uso sono tanti! Fate qualche esperimento e sbizzarritevi per impratichirvi con questo servizio, non c’è nulla di complicato.

Ecco invece l’esempio completo, se volete vedere il risultato finale! (Non fate i pigri e provate a riscriverlo da soli 😉 )

Ti è piaciuto l’articolo? Condividilo!

Rilasciare risorse gratuite non è facile: un sacco di tempo vola fra la ricerca dell’idea, la stesura, la preparazione di codice funzionante e la revisione. Se ti piace ciò che facciamo, condividi l’articolo: nel peggiore dei casi ti daranno del secchione!

Ti interesserà anche…

Angular

Creazione di Guardie per i Moduli

I moduli sono una delle feature che più amo di Angular: ci permettono di racchiudere componenti, servizi, pipe e direttive all’interno di un unico contenitore. Ma non si tratta soltanto…
Angular

NgRx: cosa cambia nella versione 8

L’ottava versione di NgRx ha portato molti cambiamenti interessanti che ci consentono di scrivere codice più pulito e più snello. E c’è anche qualche chicca che vale la pena di…

Iscriviti alla nostra newsletter!

Rimani aggiornato per quando rilasceremo nuovi articoli, nuovi corsinuove promozioni e nuove risorse gratuite. Ti promettiamo che useremo la tua email con prudenza, e non la condivideremo con nessuno senza il tuo consenso!

Abbiamo a cuore la tua privacy. Niente spam. Leggi la nostra privacy policy qui.

Menu