Tutorials Logic
Tutorials Logic, IN info@tutorialslogic.com
Navigation
Home About Us Contact Us Blogs FAQs
Tutorials
All Tutorials
Services
Academic Projects Resume Writing Website Development
Practice
Quiz Challenge Interview Questions Certification Practice
Tools
Online Compiler JSON Formatter Regex Tester CSS Unit Converter Color Picker
Compiler Tools

Angular Animations — animate.enter, animate.leave, CSS

Angular Animations

Animations make an Angular application feel smoother and easier to understand. A good animation explains that something has entered the screen, left the screen, changed state, expanded, collapsed, loaded, or moved because of the user's action. The goal is not to decorate every element. The goal is to guide attention and make interface changes feel intentional.

Modern Angular recommends using CSS-based animations with animate.enter and animate.leave for new code. These APIs let Angular add animation classes at the right moment while CSS handles the actual motion. Older Angular projects may still use the @angular/animations package with trigger(), state(), and transition(), so this tutorial covers both approaches.

What You Should Animate

  • Elements that appear or disappear, such as alerts, panels, modals, dropdowns, and list items.
  • State changes such as open/closed, selected/unselected, valid/invalid, and loading/ready.
  • Navigation or layout changes where users need help understanding what changed.
  • Small feedback moments such as button press states, focus rings, and successful form submission.

Animations should be short, purposeful, and predictable. Most UI animations work best between 120ms and 300ms. Slower animations are useful for large layout transitions, but they can make everyday actions feel delayed if overused.

Modern Angular Approach

For new Angular applications, prefer animate.enter and animate.leave. They are compiler-supported animation bindings that work with CSS transitions or CSS keyframes. You do not need to import BrowserAnimationsModule or call provideAnimationsAsync() just to use these modern enter and leave animations.

The mental model is simple: Angular controls when the class is applied, and CSS controls how the element moves. When the animation finishes, Angular removes the temporary animation class.

Basic Enter Animation
import { Component, signal } from '@angular/core';

@Component({
  selector: 'app-notice',
  standalone: true,
  template: `
    <button type="button" (click)="show.set(!show())">
      Toggle notice
    </button>

    @if (show()) {
      <div class="notice" animate.enter="notice-enter">
        Profile saved successfully.
      </div>
    }
  `,
  styleUrl: './notice.component.css'
})
export class NoticeComponent {
  show = signal(false);
}
.notice {
  margin-top: 12px;
  padding: 12px 14px;
  border-left: 4px solid #16a34a;
  background: #f0fdf4;
  color: #166534;
}

.notice-enter {
  animation: slide-fade-in 220ms ease-out;
}

@keyframes slide-fade-in {
  from {
    opacity: 0;
    transform: translateY(8px);
  }
  to {
    opacity: 1;
    transform: translateY(0);
  }
}

This example uses modern control flow with @if. When show() becomes true, Angular adds the element to the DOM and applies the notice-enter class while the animation runs.

Enter and Leave Together

Most real UI elements need both an entrance and an exit. Use animate.enter for the class that runs when the element is inserted, and animate.leave for the class that runs before Angular removes the element.

Toast Enter and Leave
import { Component, signal } from '@angular/core';

@Component({
  selector: 'app-toast-demo',
  standalone: true,
  template: `
    <button type="button" (click)="visible.set(true)">Show toast</button>
    <button type="button" (click)="visible.set(false)">Hide toast</button>

    @if (visible()) {
      <div
        class="toast"
        animate.enter="toast-enter"
        animate.leave="toast-leave"
      >
        New message received.
      </div>
    }
  `,
  styleUrl: './toast.component.css'
})
export class ToastDemoComponent {
  visible = signal(false);
}
.toast {
  width: fit-content;
  margin-top: 12px;
  padding: 10px 14px;
  border-radius: 6px;
  background: #111827;
  color: white;
  box-shadow: 0 8px 24px rgba(15, 23, 42, .18);
}

.toast-enter {
  animation: toast-in 180ms ease-out;
}

.toast-leave {
  animation: toast-out 150ms ease-in forwards;
}

@keyframes toast-in {
  from { opacity: 0; transform: translateY(10px) scale(.98); }
  to   { opacity: 1; transform: translateY(0) scale(1); }
}

@keyframes toast-out {
  from { opacity: 1; transform: translateY(0) scale(1); }
  to   { opacity: 0; transform: translateY(8px) scale(.98); }
}

The forwards value in the leave animation keeps the final hidden style while Angular waits for the animation to finish and then removes the element from the DOM.

Using CSS Transitions Instead of Keyframes

Keyframes are good for precise multi-step motion. CSS transitions are better for simple changes between two states. With animate.enter, the animation class can define the final state and a starting style can be provided with @starting-style.

Transition with @starting-style
@if (open()) {
  <section class="panel" animate.enter="panel-enter">
    <h4>Account details</h4>
    <p>This panel fades and slides into place.</p>
  </section>
}
.panel {
  padding: 16px;
  border: 1px solid #dbeafe;
  background: #eff6ff;
}

.panel-enter {
  opacity: 1;
  transform: translateY(0);
  transition: opacity 180ms ease-out, transform 180ms ease-out;

  @starting-style {
    opacity: 0;
    transform: translateY(10px);
  }
}

Dynamic Animation Classes

animate.enter and animate.leave can accept a string or an expression. This is useful when a component supports different motion styles, such as sliding from the left or right depending on the action.

Dynamic Animation Class
import { Component, computed, signal } from '@angular/core';

@Component({
  selector: 'app-drawer',
  standalone: true,
  template: `
    <button type="button" (click)="side.set('left'); open.set(true)">Left</button>
    <button type="button" (click)="side.set('right'); open.set(true)">Right</button>
    <button type="button" (click)="open.set(false)">Close</button>

    @if (open()) {
      <aside class="drawer" [animate.enter]="enterClass()" animate.leave="drawer-leave">
        Drawer from the {{ side() }} side.
      </aside>
    }
  `
})
export class DrawerComponent {
  open = signal(false);
  side = signal<'left' | 'right'>('left');
  enterClass = computed(() => this.side() === 'left' ? 'drawer-left' : 'drawer-right');
}

The matching CSS classes would define different transforms, for example translateX(-16px) for a left drawer and translateX(16px) for a right drawer.

Animating Lists

Lists are one of the best places to use animations because adding and removing rows can otherwise feel abrupt. With modern Angular, use @for for rendering and animate.enter / animate.leave on each repeated item.

List Item Animation
import { Component, signal } from '@angular/core';

type Task = { id: number; title: string };

@Component({
  selector: 'app-tasks',
  standalone: true,
  template: `
    <button type="button" (click)="addTask()">Add task</button>

    <ul class="task-list">
      @for (task of tasks(); track task.id) {
        <li class="task" animate.enter="task-enter" animate.leave="task-leave">
          <span>{{ task.title }}</span>
          <button type="button" (click)="removeTask(task.id)">Done</button>
        </li>
      } @empty {
        <li class="empty">No tasks left.</li>
      }
    </ul>
  `,
  styleUrl: './tasks.component.css'
})
export class TasksComponent {
  private nextId = 3;
  tasks = signal<Task[]>([
    { id: 1, title: 'Create animation classes' },
    { id: 2, title: 'Keep transitions short' }
  ]);

  addTask() {
    this.tasks.update(items => [
      ...items,
      { id: this.nextId, title: `Task ${this.nextId++}` }
    ]);
  }

  removeTask(id: number) {
    this.tasks.update(items => items.filter(task => task.id !== id));
  }
}
.task-list {
  display: grid;
  gap: 8px;
  padding: 0;
  list-style: none;
}

.task {
  display: flex;
  justify-content: space-between;
  gap: 12px;
  padding: 10px 12px;
  border: 1px solid #e5e7eb;
  border-radius: 6px;
}

.task-enter {
  animation: task-in 180ms ease-out;
}

.task-leave {
  animation: task-out 140ms ease-in forwards;
}

@keyframes task-in {
  from { opacity: 0; transform: translateX(-10px); }
  to   { opacity: 1; transform: translateX(0); }
}

@keyframes task-out {
  from { opacity: 1; transform: translateX(0); }
  to   { opacity: 0; transform: translateX(10px); }
}

Animating State Changes with CSS Classes

Not every animation needs animate.enter or animate.leave. If an element remains in the DOM and only changes state, use class binding, style binding, or CSS transitions.

Open and Closed State
import { Component, signal } from '@angular/core';

@Component({
  selector: 'app-accordion',
  standalone: true,
  template: `
    <button type="button" (click)="open.update(value => !value)">
      Toggle details
    </button>

    <div class="details" [class.details-open]="open()">
      <p>This content expands with a CSS transition.</p>
    </div>
  `,
  styleUrl: './accordion.component.css'
})
export class AccordionComponent {
  open = signal(false);
}
.details {
  max-height: 0;
  overflow: hidden;
  opacity: 0;
  transition: max-height 240ms ease, opacity 180ms ease;
}

.details-open {
  max-height: 160px;
  opacity: 1;
}

This is often the cleanest approach for accordions, menus, badges, and validation states because Angular only needs to toggle a class.

Accessibility and Reduced Motion

Animations should respect users who prefer reduced motion. CSS makes this straightforward with the prefers-reduced-motion media query. In reduced-motion mode, keep important visual state changes but remove large movement and long animation durations.

Reduced Motion CSS
@media (prefers-reduced-motion: reduce) {
  *,
  *::before,
  *::after {
    animation-duration: 1ms !important;
    animation-iteration-count: 1 !important;
    transition-duration: 1ms !important;
    scroll-behavior: auto !important;
  }
}

Performance Best Practices

  • Prefer animating opacity and transform. They are usually cheaper than animating layout properties.
  • Avoid animating width, height, top, left, and large shadows on many elements at once.
  • Keep animations short and avoid long chains of delayed motion in everyday workflows.
  • Make sure the final UI state is clear even when animations are disabled.
  • Use stable track expressions in @for lists so Angular can animate the correct item.

Legacy Angular Animations Package

Older Angular tutorials and many existing projects use the @angular/animations package. In this model, animations are written in TypeScript using trigger(), state(), style(), animate(), and transition(). Angular has deprecated this package for new animation work, but understanding it is still useful when maintaining older apps.

If an existing app still uses this package, standalone applications typically enable it with provideAnimationsAsync() in application configuration. NgModule-based applications often use BrowserAnimationsModule.

Enable Legacy Animations
import { ApplicationConfig } from '@angular/core';
import { provideAnimationsAsync } from '@angular/platform-browser/animations/async';
import { provideRouter } from '@angular/router';
import { routes } from './app.routes';

export const appConfig: ApplicationConfig = {
  providers: [
    provideRouter(routes),
    provideAnimationsAsync()
  ]
};
Legacy Trigger Example
import { Component, signal } from '@angular/core';
import { animate, state, style, transition, trigger } from '@angular/animations';

@Component({
  selector: 'app-expand-card',
  standalone: true,
  animations: [
    trigger('expandCollapse', [
      state('open', style({
        height: '*',
        opacity: 1
      })),
      state('closed', style({
        height: '0',
        opacity: 0,
        overflow: 'hidden'
      })),
      transition('open <=> closed', [
        animate('220ms ease-in-out')
      ])
    ])
  ],
  template: `
    <button type="button" (click)="open.update(value => !value)">
      Toggle card
    </button>

    <section [@expandCollapse]="open() ? 'open' : 'closed'">
      <p>This uses the legacy Angular animations package.</p>
    </section>
  `
})
export class ExpandCardComponent {
  open = signal(false);
}

Legacy Animation States

The legacy package uses state expressions to decide which transition should run. These are common expressions you will see in existing Angular code:

  • open => closed runs only when the state changes from open to closed.
  • open <=> closed runs in both directions.
  • * is the wildcard state and matches any state.
  • void means the element is not currently in the DOM.
  • :enter is a shortcut for void => *.
  • :leave is a shortcut for * => void.

When to Use Which Approach

Need Recommended approach
Element appears or disappears animate.enter and animate.leave
Simple hover, focus, selected, or expanded state CSS transitions with Angular class binding
Repeated list item insertion/removal @for with animate.enter / animate.leave on each item
Maintaining older trigger/state code Legacy @angular/animations APIs
Complex timeline or physics animation CSS keyframes or a dedicated animation library

Summary

In modern Angular, start with CSS and use animate.enter and animate.leave when Angular needs to coordinate DOM insertion or removal. Use normal class bindings and CSS transitions for elements that remain on the page and only change state. Learn the older @angular/animations trigger syntax so you can maintain existing code, but prefer the modern CSS-based approach for new Angular applications.

Key Takeaways
  • Modern Angular animations usually use animate.enter and animate.leave with CSS.
  • Use CSS transitions for state changes when the element stays in the DOM.
  • Use CSS keyframes when an animation needs a clear multi-step motion path.
  • Prefer animating opacity and transform for smoother performance.
  • Respect prefers-reduced-motion so users can reduce large or distracting movement.
  • The older @angular/animations package is still common in existing projects, but it is deprecated for new animation work.
Common Mistakes to Avoid
WRONG Using the legacy @angular/animations package for every new animation
RIGHT Use animate.enter, animate.leave, and CSS for new enter/leave effects
The modern API keeps most animation code in CSS and avoids pulling in the legacy animation engine for simple UI motion.
WRONG Animating height, top, left, or width across many elements
RIGHT Animate transform and opacity whenever possible
Layout-heavy animations can cause jank because the browser has to recalculate layout repeatedly.
WRONG Forgetting reduced-motion users
RIGHT Add a prefers-reduced-motion rule
Good animation design includes a calmer path for users who prefer less motion.

Frequently Asked Questions


Ready to Level Up Your Skills?

Explore 500+ free tutorials across 20+ languages and frameworks.