Guides
Animate on enter and leave
Animate an element when Angular creates it, shape the timing, and let it finish leaving before it is removed.
Use MoveEnterDirective when an element should animate once, as Angular creates it: a card that fades up, a toast that slides in, a heading that rises. This is the smallest useful piece of Angular Movement.
Animate an element when it enters
@Component({
selector: 'app-card',
imports: [MoveEnterDirective],
template: `<article moveEnter="fade-up">Billing summary</article>`,
})
export class Card {}
moveEnter takes a preset name or your own keyframes. It runs once, when the directive initialises, and ignores later input changes. That is by design: it describes an entrance, not a state. To animate between states when a signal changes, use reactive animation and variants.
moveEnter
The real directive. moveEnter plays once when the element is created, so each change recreates the element to replay it.
fade-up
entered 1×
No events yet. Interact with the preview to see them here.
Template
<div
moveEnter="fade-up"
[moveDuration]="500"
[moveDelay]="0"
moveEasing="ease-out"
>Content</div>The playground wraps the directive in a small component. Because moveEnter is init-only, changing a control (or pressing Replay) recreates the element, which plays the animation again — the page is not pretending the directive is reactive. Open the full playground for every preset.
Shape the timing
Duration, delay and easing are plain inputs shared by every directive. A bare number is milliseconds; a string states its unit.
@Component({
selector: 'app-toast',
imports: [MoveEnterDirective],
template: `
<div moveEnter="slide-up" moveDuration="300ms" moveDelay="100ms" moveEasing="ease-out">
Saved
</div>
`,
})
export class Toast {}
Defaults for the whole app come from provideMovement; the input on the element always wins.
Use your own keyframes
Presets are shorthand for keyframes. Pass an object to animate anything the Web Animations API can animate — keyframes are typed for the common shorthands (x, y, scale, rotate, blur, opacity, …) and any other CSS property passes straight through.
@Component({
selector: 'app-wipe',
imports: [MoveEnterDirective],
template: `
<!-- Any CSS property the Web Animations API can animate, not just presets. -->
<div [moveEnter]="{ clipPath: ['inset(0 100% 0 0)', 'inset(0 0% 0 0)'] }">Wipe</div>
<div [moveEnter]="{ opacity: [0, 1], y: [16, 0] }" moveDuration="400ms">Rise</div>
`,
})
export class Wipe {}
Let an element finish leaving
MoveLeaveDirective describes an exit, but it cannot hold a node that Angular has already removed. @if destroys its view synchronously, so there is nothing left to animate. Wrap the content in *movePresence, which keeps the view in the DOM until every leave animation inside it has finished:
@Component({
selector: 'app-details',
imports: [MovePresenceDirective, MoveEnterDirective, MoveLeaveDirective],
template: `
<button type="button" (click)="open.set(!open())">Toggle</button>
<ng-container *movePresence="open()">
<aside moveEnter="fade-up" moveLeave="fade-down">Details</aside>
</ng-container>
`,
})
export class Details {
protected readonly open = signal(false);
}
See Presence for the full model, including keyed lists.
[move] and moveAnimate
MoveAnimateDirective ([move], [moveAnimate]) is the shorthand for an entrance plus an exit on one element, with moveInitial and moveExit states. Like moveEnter it is one-shot.
@Component({
selector: 'app-hero-title',
imports: [MoveAnimateDirective],
template: `<h1 [moveInitial]="{ opacity: 0, y: 24 }" [moveAnimate]="{ opacity: 1, y: 0 }">
Hi
</h1>`,
})
export class HeroTitle {}
Limits to know
- Both directives are no-ops on the server and start after hydration. For an above-the-fold entrance that must paint before the app boots, use CSS; see Accessibility and troubleshooting.
- Reduced motion jumps straight to the end state automatically.
- Unsure which directive to pick? Choose an API.
Related: moveEnter playground · moveLeave playground · moveAnimate playground · MoveEnterDirective