Online

Introduction to Angular animations

Animation provides the illusion of motion: HTML elements change styles over time. Well-designed animations can make your application more intuitive and engaging, but they aren't just cosmetic. Animations can improve your application and the user experience in a number of ways:

  • Without animations, web page transitions can seem abrupt and jarring
  • Motion greatly enhances the user experience, so animations give users a chance to detect the application's response to their actions
  • Good animations intuitively call the user's attention to where it is needed

Typically, animations involve multiple style transformations over time. An HTML element can move, change color, grow or shrink, fade, or slide off the page. These changes can occur simultaneously or sequentially. You can control the timing of each transformation.

Angular's animation system is built on CSS functionality, which means you can animate any property that the browser considers animatable. This includes positions, sizes, transforms, colors, borders, and more. The W3C maintains a list of animatable properties on its CSS Transitions page.

About this guide

This guide covers the basic Angular animation features to get you started on adding Angular animations to your project.

Getting started

The main Angular modules for animations are @angular/animations and @angular/platform-browser.

To get started with adding Angular animations to your project, import the animation-specific modules along with standard Angular functionality.

Import provideAnimationsAsync from @angular/platform-browser/animations/async and add it to the providers list in the bootstrapApplication function call.

Enabling Animations
bootstrapApplication(AppComponent, {
  providers: [provideAnimationsAsync()],
});

For NgModule based applications import BrowserAnimationsModule, which introduces the animation capabilities into your Angular root application module.

app.module.ts
import {NgModule} from '@angular/core';
import {BrowserModule} from '@angular/platform-browser';
import {BrowserAnimationsModule} from '@angular/platform-browser/animations';

@NgModule({
  imports: [BrowserModule, BrowserAnimationsModule],
  declarations: [],
  bootstrap: [],
})
export class AppModule {}

If you plan to use specific animation functions in component files, import those functions from @angular/animations.

app.ts
// #docplaster
// #docregion imports
import {Component, HostBinding, inject} from '@angular/core';
import {
  trigger,
  state,
  style,
  animate,
  transition,
  // ...
} from '@angular/animations';

// #enddocregion imports
import {ChildrenOutletContexts, RouterLink, RouterOutlet} from '@angular/router';
import {slideInAnimation} from './animations';

// #docregion decorator, toggle-app-animations, define
@Component({
  selector: 'app-root',
  templateUrl: 'app.html',
  styleUrls: ['app.css'],
  imports: [RouterLink, RouterOutlet],
  animations: [
    // #enddocregion decorator
    slideInAnimation,
    // #docregion decorator
    // #enddocregion toggle-app-animations, define
    // animation triggers go here
    // #docregion toggle-app-animations, define
  ],
})
// #enddocregion decorator, define
export class AppComponent {
  @HostBinding('@.disabled')
  public animationsDisabled = false;
  // #enddocregion toggle-app-animations

  // #docregion get-route-animations-data
  private contexts = inject(ChildrenOutletContexts);

  getRouteAnimationData() {
    return this.contexts.getContext('primary')?.route?.snapshot?.data?.['animation'];
  }
  // #enddocregion get-route-animations-data

  toggleAnimations() {
    this.animationsDisabled = !this.animationsDisabled;
  }
  // #docregion toggle-app-animations
}
// #enddocregion toggle-app-animations

See all available animation functions at the end of this guide.

In the component file, add a metadata property called animations: within the @Component() decorator. You put the trigger that defines an animation within the animations metadata property.

app.ts
// #docplaster
// #docregion imports
import {Component, HostBinding, inject} from '@angular/core';
import {
  trigger,
  state,
  style,
  animate,
  transition,
  // ...
} from '@angular/animations';

// #enddocregion imports
import {ChildrenOutletContexts, RouterLink, RouterOutlet} from '@angular/router';
import {slideInAnimation} from './animations';

// #docregion decorator, toggle-app-animations, define
@Component({
  selector: 'app-root',
  templateUrl: 'app.html',
  styleUrls: ['app.css'],
  imports: [RouterLink, RouterOutlet],
  animations: [
    // #enddocregion decorator
    slideInAnimation,
    // #docregion decorator
    // #enddocregion toggle-app-animations, define
    // animation triggers go here
    // #docregion toggle-app-animations, define
  ],
})
// #enddocregion decorator, define
export class AppComponent {
  @HostBinding('@.disabled')
  public animationsDisabled = false;
  // #enddocregion toggle-app-animations

  // #docregion get-route-animations-data
  private contexts = inject(ChildrenOutletContexts);

  getRouteAnimationData() {
    return this.contexts.getContext('primary')?.route?.snapshot?.data?.['animation'];
  }
  // #enddocregion get-route-animations-data

  toggleAnimations() {
    this.animationsDisabled = !this.animationsDisabled;
  }
  // #docregion toggle-app-animations
}
// #enddocregion toggle-app-animations

Animating a transition

Let's animate a transition that changes a single HTML element from one state to another. For example, you can specify that a button displays either Open or Closed based on the user's last action. When the button is in the open state, it's visible and yellow. When it's in the closed state, it's translucent and blue.

In HTML, these attributes are set using ordinary CSS styles such as color and opacity. In Angular, use the style() function to specify a set of CSS styles for use with animations. Collect a set of styles in an animation state, and give the state a name, such as open or closed.

Run the following command in terminal to generate the component:

shell
ng g component open-close

This will create the component at src/app/open-close.ts.

Animation state and styles

Use Angular's state() function to define different states to call at the end of each transition. This function takes two arguments: A unique name like open or closed and a style() function.

Use the style() function to define a set of styles to associate with a given state name. You must use camelCase for style attributes that contain dashes, such as backgroundColor or wrap them in quotes, such as 'background-color'.

Let's see how Angular's state() function works with the style⁣­(⁠) function to set CSS style attributes. In this code snippet, multiple style attributes are set at the same time for the state. In the open state, the button has a height of 200 pixels, an opacity of 1, and a yellow background color.

open-close.ts
// #docplaster
import {Component, input} from '@angular/core';
import {trigger, transition, state, animate, style, AnimationEvent} from '@angular/animations';

// #docregion component, events1
@Component({
  selector: 'app-open-close',
  // #docregion trigger-wildcard1, trigger-transition
  animations: [
    trigger('openClose', [
      // #docregion state1
      // ...
      // #enddocregion events1
      state(
        'open',
        style({
          height: '200px',
          opacity: 1,
          backgroundColor: 'yellow',
        }),
      ),
      // #enddocregion state1
      // #docregion state2
      state(
        'closed',
        style({
          height: '100px',
          opacity: 0.8,
          backgroundColor: 'blue',
        }),
      ),
      // #enddocregion state2, trigger-wildcard1
      // #docregion transition1
      transition('open => closed', [animate('1s')]),
      // #enddocregion transition1
      // #docregion transition2
      transition('closed => open', [animate('0.5s')]),
      // #enddocregion transition2, component
      // #docregion trigger-wildcard1
      transition('* => closed', [animate('1s')]),
      transition('* => open', [animate('0.5s')]),
      // #enddocregion trigger-wildcard1
      // #docregion trigger-wildcard2
      transition('open <=> closed', [animate('0.5s')]),
      // #enddocregion trigger-wildcard2
      // #docregion transition4
      transition('* => open', [animate('1s', style({opacity: '*'}))]),
      // #enddocregion transition4
      transition('* => *', [animate('1s')]),
      // #enddocregion trigger-transition
      // #docregion component, trigger-wildcard1, events1
    ]),
  ],
  // #enddocregion trigger-wildcard1
  templateUrl: 'open-close.html',
  styleUrls: ['open-close.css'],
})
// #docregion events
export class OpenClose {
  // #enddocregion events1, events, component
  logging = input(false);
  // #docregion component
  isOpen = true;

  toggle() {
    this.isOpen = !this.isOpen;
  }

  // #enddocregion component
  // #docregion events1, events
  onAnimationEvent(event: AnimationEvent) {
    // #enddocregion events1, events
    if (!this.logging) {
      return;
    }
    // #docregion events
    // openClose is trigger name in this example
    console.warn(`Animation Trigger: ${event.triggerName}`);

    // phaseName is "start" or "done"
    console.warn(`Phase: ${event.phaseName}`);

    // in our example, totalTime is 1000 (number of milliseconds in a second)
    console.warn(`Total time: ${event.totalTime}`);

    // in our example, fromState is either "open" or "closed"
    console.warn(`From: ${event.fromState}`);

    // in our example, toState either "open" or "closed"
    console.warn(`To: ${event.toState}`);

    // the HTML element itself, the button in this case
    console.warn(`Element: ${event.element}`);
    // #docregion events1
  }
  // #docregion component
}
// #enddocregion component

In the following closed state, the button has a height of 100 pixels, an opacity of 0.8, and a background color of blue.

open-close.ts
// #docplaster
import {Component, input} from '@angular/core';
import {trigger, transition, state, animate, style, AnimationEvent} from '@angular/animations';

// #docregion component, events1
@Component({
  selector: 'app-open-close',
  // #docregion trigger-wildcard1, trigger-transition
  animations: [
    trigger('openClose', [
      // #docregion state1
      // ...
      // #enddocregion events1
      state(
        'open',
        style({
          height: '200px',
          opacity: 1,
          backgroundColor: 'yellow',
        }),
      ),
      // #enddocregion state1
      // #docregion state2
      state(
        'closed',
        style({
          height: '100px',
          opacity: 0.8,
          backgroundColor: 'blue',
        }),
      ),
      // #enddocregion state2, trigger-wildcard1
      // #docregion transition1
      transition('open => closed', [animate('1s')]),
      // #enddocregion transition1
      // #docregion transition2
      transition('closed => open', [animate('0.5s')]),
      // #enddocregion transition2, component
      // #docregion trigger-wildcard1
      transition('* => closed', [animate('1s')]),
      transition('* => open', [animate('0.5s')]),
      // #enddocregion trigger-wildcard1
      // #docregion trigger-wildcard2
      transition('open <=> closed', [animate('0.5s')]),
      // #enddocregion trigger-wildcard2
      // #docregion transition4
      transition('* => open', [animate('1s', style({opacity: '*'}))]),
      // #enddocregion transition4
      transition('* => *', [animate('1s')]),
      // #enddocregion trigger-transition
      // #docregion component, trigger-wildcard1, events1
    ]),
  ],
  // #enddocregion trigger-wildcard1
  templateUrl: 'open-close.html',
  styleUrls: ['open-close.css'],
})
// #docregion events
export class OpenClose {
  // #enddocregion events1, events, component
  logging = input(false);
  // #docregion component
  isOpen = true;

  toggle() {
    this.isOpen = !this.isOpen;
  }

  // #enddocregion component
  // #docregion events1, events
  onAnimationEvent(event: AnimationEvent) {
    // #enddocregion events1, events
    if (!this.logging) {
      return;
    }
    // #docregion events
    // openClose is trigger name in this example
    console.warn(`Animation Trigger: ${event.triggerName}`);

    // phaseName is "start" or "done"
    console.warn(`Phase: ${event.phaseName}`);

    // in our example, totalTime is 1000 (number of milliseconds in a second)
    console.warn(`Total time: ${event.totalTime}`);

    // in our example, fromState is either "open" or "closed"
    console.warn(`From: ${event.fromState}`);

    // in our example, toState either "open" or "closed"
    console.warn(`To: ${event.toState}`);

    // the HTML element itself, the button in this case
    console.warn(`Element: ${event.element}`);
    // #docregion events1
  }
  // #docregion component
}
// #enddocregion component

Transitions and timing

In Angular, you can set multiple styles without any animation. However, without further refinement, the button instantly transforms with no fade, no shrinkage, or other visible indicator that a change is occurring.

To make the change less abrupt, you need to define an animation transition to specify the changes that occur between one state and another over a period of time. The transition() function accepts two arguments: The first argument accepts an expression that defines the direction between two transition states, and the second argument accepts one or a series of animate() steps.

Use the animate() function to define the length, delay, and easing of a transition, and to designate the style function for defining styles while transitions are taking place. Use the animate() function to define the keyframes() function for multi-step animations. These definitions are placed in the second argument of the animate() function.

Animation metadata: duration, delay, and easing

The animate() function \(second argument of the transition function\) accepts the timings and styles input parameters.

The timings parameter takes either a number or a string defined in three parts.

ts
animate(duration);

or

ts
animate('duration delay easing');

The first part, duration, is required. The duration can be expressed in milliseconds as a number without quotes, or in seconds with quotes and a time specifier. For example, a duration of a tenth of a second can be expressed as follows:

100

  • As a plain number, in milliseconds:

'100ms'

  • In a string, as milliseconds:

'0.1s'

  • In a string, as seconds:

The second argument, delay, has the same syntax as duration. For example:

  • Wait for 100ms and then run for 200ms: '0.2s 100ms'

The third argument, easing, controls how the animation accelerates and decelerates during its runtime. For example, ease-in causes the animation to begin slowly, and to pick up speed as it progresses.

Use a deceleration curve to start out fast and slowly decelerate to a resting point: '0.2s 100ms ease-out'

  • Wait for 100ms, run for 200ms.

Use a standard curve to start slow, accelerate in the middle, and then decelerate slowly at the end: '0.2s ease-in-out'

  • Run for 200ms, with no delay.

Use an acceleration curve to start slow and end at full velocity: '0.2s ease-in'

  • Start immediately, run for 200ms.

This example provides a state transition from open to closed with a 1-second transition between states.

open-close.ts
// #docplaster
import {Component, input} from '@angular/core';
import {trigger, transition, state, animate, style, AnimationEvent} from '@angular/animations';

// #docregion component, events1
@Component({
  selector: 'app-open-close',
  // #docregion trigger-wildcard1, trigger-transition
  animations: [
    trigger('openClose', [
      // #docregion state1
      // ...
      // #enddocregion events1
      state(
        'open',
        style({
          height: '200px',
          opacity: 1,
          backgroundColor: 'yellow',
        }),
      ),
      // #enddocregion state1
      // #docregion state2
      state(
        'closed',
        style({
          height: '100px',
          opacity: 0.8,
          backgroundColor: 'blue',
        }),
      ),
      // #enddocregion state2, trigger-wildcard1
      // #docregion transition1
      transition('open => closed', [animate('1s')]),
      // #enddocregion transition1
      // #docregion transition2
      transition('closed => open', [animate('0.5s')]),
      // #enddocregion transition2, component
      // #docregion trigger-wildcard1
      transition('* => closed', [animate('1s')]),
      transition('* => open', [animate('0.5s')]),
      // #enddocregion trigger-wildcard1
      // #docregion trigger-wildcard2
      transition('open <=> closed', [animate('0.5s')]),
      // #enddocregion trigger-wildcard2
      // #docregion transition4
      transition('* => open', [animate('1s', style({opacity: '*'}))]),
      // #enddocregion transition4
      transition('* => *', [animate('1s')]),
      // #enddocregion trigger-transition
      // #docregion component, trigger-wildcard1, events1
    ]),
  ],
  // #enddocregion trigger-wildcard1
  templateUrl: 'open-close.html',
  styleUrls: ['open-close.css'],
})
// #docregion events
export class OpenClose {
  // #enddocregion events1, events, component
  logging = input(false);
  // #docregion component
  isOpen = true;

  toggle() {
    this.isOpen = !this.isOpen;
  }

  // #enddocregion component
  // #docregion events1, events
  onAnimationEvent(event: AnimationEvent) {
    // #enddocregion events1, events
    if (!this.logging) {
      return;
    }
    // #docregion events
    // openClose is trigger name in this example
    console.warn(`Animation Trigger: ${event.triggerName}`);

    // phaseName is "start" or "done"
    console.warn(`Phase: ${event.phaseName}`);

    // in our example, totalTime is 1000 (number of milliseconds in a second)
    console.warn(`Total time: ${event.totalTime}`);

    // in our example, fromState is either "open" or "closed"
    console.warn(`From: ${event.fromState}`);

    // in our example, toState either "open" or "closed"
    console.warn(`To: ${event.toState}`);

    // the HTML element itself, the button in this case
    console.warn(`Element: ${event.element}`);
    // #docregion events1
  }
  // #docregion component
}
// #enddocregion component

In the preceding code snippet, the => operator indicates unidirectional transitions, and <=> is bidirectional. Within the transition, animate() specifies how long the transition takes. In this case, the state change from open to closed takes 1 second, expressed here as 1s.

This example adds a state transition from the closed state to the open state with a 0.5-second transition animation arc.

open-close.ts
// #docplaster
import {Component, input} from '@angular/core';
import {trigger, transition, state, animate, style, AnimationEvent} from '@angular/animations';

// #docregion component, events1
@Component({
  selector: 'app-open-close',
  // #docregion trigger-wildcard1, trigger-transition
  animations: [
    trigger('openClose', [
      // #docregion state1
      // ...
      // #enddocregion events1
      state(
        'open',
        style({
          height: '200px',
          opacity: 1,
          backgroundColor: 'yellow',
        }),
      ),
      // #enddocregion state1
      // #docregion state2
      state(
        'closed',
        style({
          height: '100px',
          opacity: 0.8,
          backgroundColor: 'blue',
        }),
      ),
      // #enddocregion state2, trigger-wildcard1
      // #docregion transition1
      transition('open => closed', [animate('1s')]),
      // #enddocregion transition1
      // #docregion transition2
      transition('closed => open', [animate('0.5s')]),
      // #enddocregion transition2, component
      // #docregion trigger-wildcard1
      transition('* => closed', [animate('1s')]),
      transition('* => open', [animate('0.5s')]),
      // #enddocregion trigger-wildcard1
      // #docregion trigger-wildcard2
      transition('open <=> closed', [animate('0.5s')]),
      // #enddocregion trigger-wildcard2
      // #docregion transition4
      transition('* => open', [animate('1s', style({opacity: '*'}))]),
      // #enddocregion transition4
      transition('* => *', [animate('1s')]),
      // #enddocregion trigger-transition
      // #docregion component, trigger-wildcard1, events1
    ]),
  ],
  // #enddocregion trigger-wildcard1
  templateUrl: 'open-close.html',
  styleUrls: ['open-close.css'],
})
// #docregion events
export class OpenClose {
  // #enddocregion events1, events, component
  logging = input(false);
  // #docregion component
  isOpen = true;

  toggle() {
    this.isOpen = !this.isOpen;
  }

  // #enddocregion component
  // #docregion events1, events
  onAnimationEvent(event: AnimationEvent) {
    // #enddocregion events1, events
    if (!this.logging) {
      return;
    }
    // #docregion events
    // openClose is trigger name in this example
    console.warn(`Animation Trigger: ${event.triggerName}`);

    // phaseName is "start" or "done"
    console.warn(`Phase: ${event.phaseName}`);

    // in our example, totalTime is 1000 (number of milliseconds in a second)
    console.warn(`Total time: ${event.totalTime}`);

    // in our example, fromState is either "open" or "closed"
    console.warn(`From: ${event.fromState}`);

    // in our example, toState either "open" or "closed"
    console.warn(`To: ${event.toState}`);

    // the HTML element itself, the button in this case
    console.warn(`Element: ${event.element}`);
    // #docregion events1
  }
  // #docregion component
}
// #enddocregion component
  • Use state() to define styles that are applied at the end of each transition, they persist after the animation completes
  • Use transition() to define intermediate styles, which create the illusion of motion during the animation
  • When animations are disabled, transition() styles can be skipped, but state() styles can't
  • Include multiple state pairs within the same transition() argument:

``ts transition('on => off, off => void'); ``

Triggering the animation

An animation requires a trigger, so that it knows when to start. The trigger() function collects the states and transitions, and gives the animation a name, so that you can attach it to the triggering element in the HTML template.

The trigger() function describes the property name to watch for changes. When a change occurs, the trigger initiates the actions included in its definition. These actions can be transitions or other functions, as we'll see later on.

In this example, we'll name the trigger openClose, and attach it to the button element. The trigger describes the open and closed states, and the timings for the two transitions.

However, it's possible for multiple triggers to be active at once.

Defining animations and attaching them to the HTML template

Animations are defined in the metadata of the component that controls the HTML element to be animated. Put the code that defines your animations under the animations: property within the @Component() decorator.

open-close.ts
// #docplaster
import {Component, input} from '@angular/core';
import {trigger, transition, state, animate, style, AnimationEvent} from '@angular/animations';

// #docregion component, events1
@Component({
  selector: 'app-open-close',
  // #docregion trigger-wildcard1, trigger-transition
  animations: [
    trigger('openClose', [
      // #docregion state1
      // ...
      // #enddocregion events1
      state(
        'open',
        style({
          height: '200px',
          opacity: 1,
          backgroundColor: 'yellow',
        }),
      ),
      // #enddocregion state1
      // #docregion state2
      state(
        'closed',
        style({
          height: '100px',
          opacity: 0.8,
          backgroundColor: 'blue',
        }),
      ),
      // #enddocregion state2, trigger-wildcard1
      // #docregion transition1
      transition('open => closed', [animate('1s')]),
      // #enddocregion transition1
      // #docregion transition2
      transition('closed => open', [animate('0.5s')]),
      // #enddocregion transition2, component
      // #docregion trigger-wildcard1
      transition('* => closed', [animate('1s')]),
      transition('* => open', [animate('0.5s')]),
      // #enddocregion trigger-wildcard1
      // #docregion trigger-wildcard2
      transition('open <=> closed', [animate('0.5s')]),
      // #enddocregion trigger-wildcard2
      // #docregion transition4
      transition('* => open', [animate('1s', style({opacity: '*'}))]),
      // #enddocregion transition4
      transition('* => *', [animate('1s')]),
      // #enddocregion trigger-transition
      // #docregion component, trigger-wildcard1, events1
    ]),
  ],
  // #enddocregion trigger-wildcard1
  templateUrl: 'open-close.html',
  styleUrls: ['open-close.css'],
})
// #docregion events
export class OpenClose {
  // #enddocregion events1, events, component
  logging = input(false);
  // #docregion component
  isOpen = true;

  toggle() {
    this.isOpen = !this.isOpen;
  }

  // #enddocregion component
  // #docregion events1, events
  onAnimationEvent(event: AnimationEvent) {
    // #enddocregion events1, events
    if (!this.logging) {
      return;
    }
    // #docregion events
    // openClose is trigger name in this example
    console.warn(`Animation Trigger: ${event.triggerName}`);

    // phaseName is "start" or "done"
    console.warn(`Phase: ${event.phaseName}`);

    // in our example, totalTime is 1000 (number of milliseconds in a second)
    console.warn(`Total time: ${event.totalTime}`);

    // in our example, fromState is either "open" or "closed"
    console.warn(`From: ${event.fromState}`);

    // in our example, toState either "open" or "closed"
    console.warn(`To: ${event.toState}`);

    // the HTML element itself, the button in this case
    console.warn(`Element: ${event.element}`);
    // #docregion events1
  }
  // #docregion component
}
// #enddocregion component

When you've defined an animation trigger for a component, attach it to an element in that component's template by wrapping the trigger name in brackets and preceding it with an @ symbol. Then, you can bind the trigger to a template expression using standard Angular property binding syntax as shown below, where triggerName is the name of the trigger, and expression evaluates to a defined animation state.

html
<div [@triggerName]="expression">…</div>

The animation is executed or triggered when the expression value changes to a new state.

The following code snippet binds the trigger to the value of the isOpen property.

open-close.html
<!-- #docplaster -->
<!-- #docregion trigger -->
<nav>
  <button type="button" (click)="toggle()">Toggle Open/Close</button>
</nav>

<div [@openClose]="isOpen ? 'open' : 'closed'" class="open-close-container">
  <p>The box is now {{ isOpen ? 'Open' : 'Closed' }}!</p>
</div>
<!-- #enddocregion trigger -->

In this example, when the isOpen expression evaluates to a defined state of open or closed, it notifies the trigger openClose of a state change. Then it's up to the openClose code to handle the state change and kick off a state change animation.

For elements entering or leaving a page \(inserted or removed from the DOM\), you can make the animations conditional. For example, use *ngIf with the animation trigger in the HTML template.

In the HTML template file, use the trigger name to attach the defined animations to the HTML element to be animated.

Code review

Here are the code files discussed in the transition example.

ts
// #docplaster
import {Component, input} from '@angular/core';
import {trigger, transition, state, animate, style, AnimationEvent} from '@angular/animations';

// #docregion component, events1
@Component({
  selector: 'app-open-close',
  // #docregion trigger-wildcard1, trigger-transition
  animations: [
    trigger('openClose', [
      // #docregion state1
      // ...
      // #enddocregion events1
      state(
        'open',
        style({
          height: '200px',
          opacity: 1,
          backgroundColor: 'yellow',
        }),
      ),
      // #enddocregion state1
      // #docregion state2
      state(
        'closed',
        style({
          height: '100px',
          opacity: 0.8,
          backgroundColor: 'blue',
        }),
      ),
      // #enddocregion state2, trigger-wildcard1
      // #docregion transition1
      transition('open => closed', [animate('1s')]),
      // #enddocregion transition1
      // #docregion transition2
      transition('closed => open', [animate('0.5s')]),
      // #enddocregion transition2, component
      // #docregion trigger-wildcard1
      transition('* => closed', [animate('1s')]),
      transition('* => open', [animate('0.5s')]),
      // #enddocregion trigger-wildcard1
      // #docregion trigger-wildcard2
      transition('open <=> closed', [animate('0.5s')]),
      // #enddocregion trigger-wildcard2
      // #docregion transition4
      transition('* => open', [animate('1s', style({opacity: '*'}))]),
      // #enddocregion transition4
      transition('* => *', [animate('1s')]),
      // #enddocregion trigger-transition
      // #docregion component, trigger-wildcard1, events1
    ]),
  ],
  // #enddocregion trigger-wildcard1
  templateUrl: 'open-close.html',
  styleUrls: ['open-close.css'],
})
// #docregion events
export class OpenClose {
  // #enddocregion events1, events, component
  logging = input(false);
  // #docregion component
  isOpen = true;

  toggle() {
    this.isOpen = !this.isOpen;
  }

  // #enddocregion component
  // #docregion events1, events
  onAnimationEvent(event: AnimationEvent) {
    // #enddocregion events1, events
    if (!this.logging) {
      return;
    }
    // #docregion events
    // openClose is trigger name in this example
    console.warn(`Animation Trigger: ${event.triggerName}`);

    // phaseName is "start" or "done"
    console.warn(`Phase: ${event.phaseName}`);

    // in our example, totalTime is 1000 (number of milliseconds in a second)
    console.warn(`Total time: ${event.totalTime}`);

    // in our example, fromState is either "open" or "closed"
    console.warn(`From: ${event.fromState}`);

    // in our example, toState either "open" or "closed"
    console.warn(`To: ${event.toState}`);

    // the HTML element itself, the button in this case
    console.warn(`Element: ${event.element}`);
    // #docregion events1
  }
  // #docregion component
}
// #enddocregion component
html
<!-- #docplaster -->
<!-- #docregion trigger -->
<nav>
  <button type="button" (click)="toggle()">Toggle Open/Close</button>
</nav>

<div [@openClose]="isOpen ? 'open' : 'closed'" class="open-close-container">
  <p>The box is now {{ isOpen ? 'Open' : 'Closed' }}!</p>
</div>
<!-- #enddocregion trigger -->
css
:host {
  display: block;
  margin-top: 1rem;
}

.open-close-container {
  border: 1px solid #dddddd;
  margin-top: 1em;
  padding: 20px 20px 0px 20px;
  color: #000000;
  font-weight: bold;
  font-size: 20px;
}

Summary

You learned to add animation to a transition between two states, using style() and state() along with animate() for the timing.

Learn about more advanced features in Angular animations under the Animation section, beginning with advanced techniques in transition and triggers.

Animations API summary

The functional API provided by the @angular/animations module provides a domain-specific language \(DSL\) for creating and controlling animations in Angular applications. See the API reference for a complete listing and syntax details of the core functions and related data structures.

Function nameWhat it does
trigger()Kicks off the animation and serves as a container for all other animation function calls. HTML template binds to triggerName. Use the first argument to declare a unique trigger name. Uses array syntax.
style()Defines one or more CSS styles to use in animations. Controls the visual appearance of HTML elements during animations. Uses object syntax.
state()Creates a named set of CSS styles that should be applied on successful transition to a given state. The state can then be referenced by name within other animation functions.
animate()Specifies the timing information for a transition. Optional values for delay and easing. Can contain style() calls within.
transition()Defines the animation sequence between two named states. Uses array syntax.
keyframes()Allows a sequential change between styles within a specified time interval. Use within animate(). Can include multiple style() calls within each keyframe(). Uses array syntax.
group()Specifies a group of animation steps \(inner animations\) to be run in parallel. Animation continues only after all inner animation steps have completed. Used within sequence() or transition().
query()Finds one or more inner HTML elements within the current element.
sequence()Specifies a list of animation steps that are run sequentially, one by one.
stagger()Staggers the starting time for animations for multiple elements.
animation()Produces a reusable animation that can be invoked from elsewhere. Used together with useAnimation().
useAnimation()Activates a reusable animation. Used with animation().
animateChild()Allows animations on child components to be run within the same timeframe as the parent.

More on Angular animations

You might also be interested in the following: