In Angular, when do animate.enter classes come off and when does animate.leave remove the element, with several animations or none at all?
answer
- end events on the element itself
- longest one wins
- no animation, no wait
- transitions need a from-state
basics
~20 sAngular waits for the longest animation or transition on the element, counting its delay, then removes the enter classes or the leaving element. If the classes start no animation, it removes them, or the element, on the next frame instead of waiting.
solid answer
~40 sAfter adding the classes, Angular measures the element's running animations and transitions, falling back to computed styles, and records the longest one, delay included; infinite animations are ignored. It listens for `animationend` and `transitionend` on that element only, ignoring events bubbling up from children, and acts when the longest one ends: enter classes come off, a leaving element is detached. If a frame later nothing is running, because the class animates nothing or a more specific selector cancelled it, Angular does not wait at all. For leaves there is also a fallback timer slightly longer than the measured duration, in case the browser never fires the end event. For an `animate.enter` class using a transition, the element needs `@starting-style`, because the class is the state being animated to.
code
ts · 21 linesimport {Component, input} from '@angular/core';
@Component({
selector: 'app-toast',
host: {'animate.leave': 'toast-out'},
template: `<p>{{ message() }}</p>`,
styles: `
:host {
display: block;
opacity: 1;
transform: none;
transition: opacity 200ms ease-out, transform 300ms ease-out;
@starting-style { opacity: 0; transform: translateX(100%); }
}
/* two transitions: removal waits for the 300ms transform, not the 200ms opacity */
:host(.toast-out) { opacity: 0; transform: translateX(100%); }
`,
})
export class Toast {
readonly message = input.required<string>();
}go deeper
Remember that Angular waits for the animation before removing a leaving element and that enter classes come off when the animation finishes.
Walk through the mechanism: measure the longest animation including delay, listen for end events on the element itself, and skip the wait when nothing animates. Explain why enter transitions need @starting-style.
Use the model to diagnose production issues: toasts vanishing early because a shorter animation was mistaken for the longest, exits skipped under reduced-motion overrides, and flaky tests where end events never fire.
Weigh relying on browser end events against explicit JavaScript completion, and decide team conventions such as keyframes for enter motion and forwards fill on exits.
## The question behind the question `animate.enter` and `animate.leave` both hand work to CSS and then have to decide **when the CSS is done**. For an entering element that decides when Angular takes the enter classes off. For a leaving element it decides something more visible: when the node is actually detached from the DOM. Interviewers ask about this because it explains the bugs people meet: a toast that vanishes before its exit finishes, a toast that never leaves, or an entry animation that simply does not play. ## How Angular finds the end 1. **Apply the classes.** Angular adds the classes named in the binding to the element. For a leave it also forces a reflow so that the browser registers the class change and starts the transition. 2. **Measure the longest animation.** On the next animation frame it asks the element which animations are running (`getAnimations()`), falling back to the computed `animation-*` and `transition-*` styles when that list is empty. For each one it adds the **delay** to the **duration** and keeps the largest. Animations that repeat forever are skipped, because they would never end. 3. **Listen on the element itself.** Angular listens for `animationend` and `transitionend`. Events that bubble up from **child** elements are ignored; only events whose target is the animated element count. 4. **Finish on the longest one.** When the end event for the longest animation arrives, Angular removes the enter classes, or removes the leave classes and detaches the element. An earlier, shorter animation ending does not trigger anything. ## When there is nothing to wait for If, one frame after the classes were added, nothing is running on the element, Angular does **not** wait. It removes the enter classes, or detaches the leaving element, immediately. This happens when: - the class sets no `animation` or `transition` at all; - a more specific selector overrides it, for example a reduced-motion rule that sets `animation: none`; - the duration works out to zero. This is deliberate: a class that intentionally animates nothing must not leave an element stuck in the DOM. ## The safety net for dropped events Browsers do not always fire end events. An element that is off-screen, or a subtree torn down quickly, can lose them. For leave animations Angular therefore also starts a **fallback timer** set to the measured duration plus a small margin. Whichever arrives first, the end event or the timer, completes the removal. This fallback applies to class-based leaves; function-based leaves have their own timeout, covered by the `animationComplete()` contract. ## Summary table | Situation | `animate.enter` | `animate.leave` | |---|---|---| | One animation | classes removed when it ends | element removed when it ends | | Several animations or transitions | waits for the longest, delay included | waits for the longest, delay included | | Class animates nothing | classes removed on the next frame | element removed on the next frame | | End event never fires | no fallback timer; classes stay until an end event arrives | fallback timer removes the element | | Infinite animation | not counted as the longest | not counted as the longest | ## The transition trap on enter A **keyframe** animation carries its own `from` values, so an enter class like `animation: toast-slide-in 250ms` just works. A **transition** only runs between two computed values. The enter class describes the state to animate *to*, and since the element is brand new there is no previous state to start from. The fix is a CSS `@starting-style` block on the element's rule that gives the starting values. Without it, the toast appears already in place, no transition runs, and, by the rule above, Angular removes the enter class on the next frame. ## Edge cases worth naming - **Reordering in `@for`.** When a tracked item only moves, Angular does not run its leave animation; only a real removal does. - **A toast shown again mid-exit.** If an `@if` re-creates the toast while the old one is still sliding out in the same place, Angular cancels the old leave and removes the old node at once, so two toasts never overlap. - **Tests.** `TestBed` disables these animations by default, so elements leave immediately; `animationsEnabled: true` in `TestBed.configureTestingModule` turns them back on, and some test environments never emit the end events at all. Knowing this timing model turns "the animation is flaky" into a short checklist: is anything actually animating, is the longest animation the one you think, and does the end event fire on the element itself?
- A reduced-motion stylesheet sets animation: none on every element. Do leaving toasts get stuck in the DOM?No. One frame after adding the leave class, Angular checks what is actually running on the element. With `animation: none` nothing is, so it removes the element immediately instead of waiting for an end event that will never come.
- Why does Angular ignore animationend events whose target is a child element?Animation and transition end events bubble. A spinner or icon animating inside the toast would otherwise end the toast's own animation early and detach it mid-motion. Angular only counts events whose target is the element carrying the binding, and stops propagation of the one that completes it.
- The user dismisses a toast and the same @if shows it again before the exit finishes. What happens to the old node?Angular tracks nodes that are still leaving. When the same template inserts a new element right where the leaving one sits, it cancels the pending leave and removes the old node at once, so the old and new toast are never on screen together.
saying these in an interview costs you the question
- Angular removes the element when the first of several animations finishes.
- A class that animates nothing leaves the element stuck in the DOM forever.
- Transition delays are ignored when Angular measures the leave duration.
- An animationend bubbling up from a child completes the parent's leave.
- An enter transition works without @starting-style because the class is the from-state.