skip to content

In Angular's legacy animations package, how do animation() and useAnimation() make an animation reusable, and how do its {{ }} parameters get values?

level: middleimportance: nice to knowfreq 18%

answer

  1. exported constant
  2. placeholders in style strings
  3. defaults in the options
  4. overrides at the call site

basics

~20 s

animation() defines a step list with {{ name }} placeholders, usually exported from a shared file; useAnimation() plays it inside a transition. Values come from params: defaults given to animation(), overridden by params passed to useAnimation(); a missing value throws.

solid answer

~40 s

`animation([...], {params: {...}})` builds a reusable animation reference. Its styles and timings can contain placeholders such as `'{{ time }}'` or `opacity: '{{ start }}'`, and the second argument's `params` supplies defaults. I export it as a constant from a shared file. In a component's trigger, `transition(':enter', useAnimation(fadeIn, {params: {time: '150ms'}}))` plays it, and the params given there override the defaults for that call. Any parameter still missing when a step runs causes an error. Parameters can also come from the template by binding the trigger to an object, `{value: state, params: {...}}`. Like the rest of `@angular/animations`, `animation()` and `useAnimation()` are deprecated since v20.2; the native replacement is a shared stylesheet of classes, with CSS custom properties playing the role of parameters.

code

ts · 23 lines
ts
import {Component, signal} from '@angular/core';
import {animate, animation, style, transition, trigger, useAnimation} from '@angular/animations';

// shared/animations.ts
export const fadeIn = animation(
  [style({opacity: '{{ from }}'}), animate('{{ time }} ease-out', style({opacity: 1}))],
  {params: {from: 0, time: '200ms'}},
);

@Component({
  selector: 'app-hint',
  template: `
    @if (shown()) {
      <p @hint>Press / to search</p>
    }
  `,
  animations: [
    trigger('hint', [transition(':enter', useAnimation(fadeIn, {params: {time: '400ms'}}))]),
  ],
})
export class Hint {
  protected readonly shown = signal(true);
}

go deeper

for a junior

Recognise animation() as a reusable definition and useAnimation() as the way to play it inside a transition.

for a middle

Explain placeholder resolution: animation() defaults, useAnimation() overrides, template params on the trigger binding, and the error on a missing value.

for a senior

Consolidate duplicated triggers into shared references in a legacy codebase, and translate them into shared CSS classes with custom-property defaults during migration.

for a principal

Decide whether a legacy shared-animation library is worth refactoring before migration or should be replaced directly by a CSS motion library.

## The problem it solves In a legacy codebase, the same fade or slide was often written into dozens of components' `animations` arrays. The `@angular/animations` package offered two functions to define motion once and reuse it: - **`animation(steps, options?)`** creates an **animation reference**: a list of `style()` and `animate()` steps that is not tied to any trigger. - **`useAnimation(reference, options?)`** plays such a reference inside a `transition()` (or inside `query()`, `group()` or `sequence()`). ## Placeholders and parameters The steps in a reusable animation can contain **interpolation placeholders** in double curly braces. They work in style values and in timing strings: ```ts export const fadeIn = animation( [style({opacity: '{{ from }}'}), animate('{{ time }} ease-out', style({opacity: 1}))], {params: {from: 0, time: '200ms'}}, ); ``` Values are resolved at the moment the animation is built for a particular transition: 1. **Defaults** come from the `params` object passed as the second argument to `animation()`. 2. **Call-site overrides** come from the `params` object passed to `useAnimation()`. Any key given there replaces the default for that use only. 3. **Template values** can be supplied by binding the trigger to an object: `[@panel]="{value: state(), params: {time: '400ms'}}"`. The `value` is what transitions match on; `params` feeds the placeholders. 4. If a placeholder has **no value** from any of these sources when a step is animated, the engine throws an error rather than guessing. ## Sharing the definition The usual layout is a file such as `animations.ts` exporting constants: - reusable references built with `animation()`, played with `useAnimation()`; - whole triggers exported as constants (`export const openCloseTrigger = trigger(...)`) and listed in several components' `animations` arrays. Exporting a whole trigger is simpler but fixes its states; exporting an `animation()` lets each component wire the motion into its own transitions and parameters. ## Options besides params The options object passed to `animation()` or `useAnimation()` may also carry a **`delay`**, which postpones the start of the referenced steps. The same options shape (delay plus params) is accepted by `group()`, `sequence()` and `animateChild()`, which is why reusable pieces compose cleanly inside larger transitions. ## Limits worth knowing | Aspect | Behaviour | |---|---| | Where placeholders work | style values and timing strings inside the referenced steps | | Missing value | error when the step is animated | | Type checking | none: `params` is an untyped object, so a misspelt key simply fails at runtime | | Tree-shaking | the constant is plain metadata, but the engine that interprets it must still be provided | ## Worked example: one fade, three components 1. A shared file exports `fadeIn = animation([...], {params: {from: 0, time: '200ms'}})`. 2. A hint component plays it with `useAnimation(fadeIn, {params: {time: '400ms'}})` on `:enter`, a slower fade because the hint is secondary. 3. A dialog component plays it with no overrides, so it gets 200 ms from 0 opacity. 4. A tooltip component starts from partial opacity with `{params: {from: 0.4}}`, keeping the default time. 5. Changing the easing in the shared file changes all three at once, which was the point of the feature. ## Common mistakes - Writing `{{time}}` in a timing string but naming the parameter `duration` in `params`: nothing checks the keys, so it fails only when the animation runs. - Passing numbers where a unit is required: `'{{ time }}'` resolved to `200` is read as milliseconds, but a style such as `height: '{{ h }}'` resolved to a bare number may not mean what you expect. - Assuming `useAnimation()` can be called on its own; it is a step, so it must sit inside a `transition()` or another step. ## The native replacement The migration guide maps this feature to plain CSS. A shared stylesheet defines classes such as `.fade-in` with `@keyframes`, and **CSS custom properties** replace the parameters: `animation-duration: var(--fade-time, 200ms)` gives a default that a component overrides with `[style.--fade-time]="'150ms'"`. Applying the class, for example through `animate.enter="fade-in"`, triggers it. The result keeps the reuse, gains cascade-based defaults, and drops the runtime engine, which is why new code in Angular 22 should not reach for `animation()` at all.

  • How would you pass a parameter from the component's state rather than hard-coding it in useAnimation()?
    Bind the trigger to an object: `[@hint]="{value: state(), params: {time: duration()}}"`. Transitions match on `value`, and the `params` object supplies values for the placeholders in the steps for that run, taking the place of hard-coded numbers.
  • What replaces animation() and useAnimation() when you migrate to native CSS?
    A shared stylesheet of reusable classes with `@keyframes`, where CSS custom properties act as parameters: `animation-duration: var(--fade-time, 200ms)` sets a default that a component overrides with a style binding. The class is applied with a class binding or with `animate.enter`, and no animation engine is needed.

saying these in an interview costs you the question

  • useAnimation() works outside a transition or other animation step.
  • A missing parameter silently falls back to zero.
  • useAnimation() params are merged into the animation() defaults permanently.
  • Placeholders only work in style values, never in timing strings.
  • animation() is the recommended way to share motion in new Angular 22 code.