Animations Angular — Angular

Angular Animations menggunakan @angular/animations untuk animasi deklaratif. Setup import { trigger, state, style, transition, animate } from…

Animasi Angular mengatur perpindahan sebuah elemen dari satu tampilan ke tampilan lain: baris keranjang yang meluncur masuk dan memudar sebelum hilang, atau badge status pesanan yang berganti warna pelan-pelan. Kamu cukup menyebut keadaan awal, keadaan akhir, dan berapa lama perpindahannya. Bayangin papan pesanan di warung Martabak Bang Jali. Kertas pesanan baru ditempel dengan diselipkan dari samping, dan kertas yang sudah selesai gak langsung dicabut: Bang Jali menurunkannya pelan dulu, baru dibuang.

🧪 Tebakan cepat: Baris keranjang memakai animate.leave="keluar", dan class .keluar menjalankan animasi CSS 200ms. Pembeli klik Buang. Berapa lama <li> itu masih ada di DOM? Jawabannya ada di bagian "animate.enter dan animate.leave".

animate.enter dan animate.leave

Sejak Angular 20.2, elemen yang masuk dan keluar DOM dianimasikan dengan dua atribut di template, dan animasinya ditulis sebagai CSS biasa. Gak perlu paket tambahan, import, atau provider:

@Component({
  selector: 'app-keranjang',
  template: `
    @for (item of keranjang(); track item.id) {
      <li animate.enter="masuk" animate.leave="keluar">
        {{ item.nama }} <button (click)="buang(item.id)">Buang</button>
      </li>
    }
  `,
  styles: `
    .masuk { animation: masuk 250ms ease-out; }
    @keyframes masuk { from { opacity: 0; transform: translateX(-16px); } }
    .keluar { animation: keluar 200ms ease-in forwards; }
    @keyframes keluar { to { opacity: 0; transform: translateX(16px); } }
  `,
})
export class KeranjangPage {
  keranjang = signal([
    { id: 1, nama: 'Martabak Manis Cokelat Keju' },
    { id: 2, nama: 'Martabak Telur Spesial' },
  ])

  buang(id: number) {
    this.keranjang.update((isi) => isi.filter((item) => item.id !== id))
  }
}

Dicoba di editor latihan (Angular 22): setelah Buang diklik, <li>-nya masih ada di DOM sekitar 200 ms dengan class keluar sambil memudar, lalu hilang. Itu jawaban tebakannya. Class masuk pada baris yang baru muncul lepas sendiri begitu animasinya selesai, sekitar 250 ms.

Class tanpa animasi di CSS-nya, misalnya animate.leave="keluar" tapi .keluar gak pernah ditulis, gak memberi Angular apa pun untuk ditunggu: barisnya langsung dibuang, sama seperti tanpa animate.leave.

Kurangi gerakan: prefers-reduced-motion

Sebagian orang pusing melihat gerakan dan menyalakan "kurangi gerakan" di HP atau laptopnya. Hormati pilihan itu di CSS-nya:

@media (prefers-reduced-motion: reduce) {
  .masuk, .keluar { animation: none; }
}

Dengan animation: none, gak ada animasi yang ditunggu, jadi baris yang dibuang langsung hilang dan baris baru langsung tampil.

Kode yang sudah ada: @angular/animations

Sebelum 20.2, Angular punya mesin animasi sendiri di paket @angular/animations. Sejak 20.2 seluruh paket itu ditandai deprecated: Use animate.enter or animate.leave instead. Intent to remove in v23. Tapi kamu masih akan sering menemuinya di proyek yang sudah ada, jadi bentuknya perlu dikenali.

Paket itu harus dipasang (npm install @angular/animations), lalu provider-nya didaftarkan sekali di app.config.ts:

// src/app/app.config.ts
import { provideAnimationsAsync } from '@angular/platform-browser/animations/async'

export const appConfig: ApplicationConfig = {
  providers: [provideAnimationsAsync()],
}

Ini output ng build (Angular 20) untuk aplikasi contoh, satu komponen dengan animasi:

Provider Initial total Diunduh belakangan
belum ada 278,16 kB
provideAnimations() 341,33 kB
provideAnimationsAsync() 286,31 kB chunk browser 64,18 kB

provideAnimations() memasukkan mesin animasinya ke bundle awal, provideAnimationsAsync() menaruhnya di chunk terpisah yang diunduh setelah halaman tampil. Lupa provider-nya, dan console menulis ERROR RuntimeError: NG05105: Unexpected synthetic property @status found. Editor latihan dan Playground menambahkan provideAnimations() sendiri begitu kodenya memakai @angular/animations.

Animasinya ditulis di TypeScript, di animations: [...] milik @Component. Badge status pesanan Bang Jali:

import { trigger, state, style, transition, animate } from '@angular/animations'

@Component({
  selector: 'app-pesanan',
  animations: [
    trigger('status', [
      state('menunggu', style({ backgroundColor: '#e2e8f0' })),
      state('dimasak', style({ backgroundColor: '#fde68a' })),
      state('selesai', style({ backgroundColor: '#bbf7d0' })),
      transition('menunggu => dimasak', animate('300ms ease-out')),
      transition('* => selesai', animate('600ms ease-in')),
      transition('* => *', animate('200ms')),
    ]),
  ],
  template: `<span class="badge" [@status]="status()">{{ status() }}</span>`,
})
export class PesananPage {
  status = signal('menunggu')
}

Elemen yang belum ada di DOM berada di state khusus bernama void. :enter adalah singkatan void => * (masuk ke DOM), :leave adalah singkatan * => void (keluar dari DOM). Baris keranjang tadi, versi lamanya:

trigger('item', [
  transition(':enter', [
    style({ opacity: 0, transform: 'translateX(-16px)' }),
    animate('250ms ease-out'),
  ]),
  transition(':leave', [
    animate('200ms ease-in', style({ opacity: 0, transform: 'translateX(16px)' })),
  ]),
])

// template
@for (item of keranjang(); track item.id) {
  <li @item>{{ item.nama }}</li>
}

Hasilnya sama dengan animate.leave: <li> yang keluar ditahan di DOM sampai animasi :leave-nya selesai. Untuk "kurangi gerakan", kode lama memakai [@.disabled]="true", yang mematikan semua animasi di elemen itu dan anak-anaknya.

⚠️ Jebakan yang sering ketemu

TL;DR: Sejak Angular 20.2, elemen yang masuk dan keluar DOM dianimasikan dengan animate.enter="kelas" dan animate.leave="kelas" di template, dan class-nya menjalankan animasi CSS biasa di styles; gak perlu import atau provider. Angular memasang class masuk lalu melepasnya setelah animasinya selesai, dan menahan elemen yang keluar di DOM sampai animasi keluarnya selesai. Class tanpa animasi membuat elemennya langsung hilang, dan animation: none di @media (prefers-reduced-motion: reduce) menghormati pengaturan "kurangi gerakan". Kode lama memakai @angular/animations (deprecated sejak 20.2): provideAnimationsAsync(), animations: [trigger('nama', [state(...), transition(...)])], [@nama], :enter = void => *, dan :leave = * => void.

Yang akan kamu pelajari