--- Update (Angular v20.2.0): ---
Well, now we have a silver bullet.
TL;DR
You can use animation attributes which will dynamically add classes on enter and leave:
<p
animate.enter="enter-transition"
animate.leave="leave-transition"
>
.enter-transition {
opacity: 1;
@starting-style {
opacity: 0;
}
}
.leave-transition {
opacity: 0;
}
No imports required. It works similar to ngClass.
If you want to achieve same result form inside a component you can use HostBinding like this:
...
export class FooComponent {
@HostBinding('animate.enter') protected readonly enter = 'enter-transition';
@HostBinding('animate.leave') protected readonly leave = 'leave-transition';
}
Or like this:
@Component({
...
host: {
'animate.enter': 'enter-transition',
'animate.leave': 'leave-transition',
},
})
...
Here is a stack blitz example.
--- Original answer (Angular v17.0.0): ---
There is no silver bullet that would solve this issue from inside the component, but it is solvable. Solution depends on what triggers construction/deconstruction of the component:
1. When the component is created/destroyed by route changes
You navigate to some page and a component that you want to animate is created. This can be solved using angular router animations, while its principal idea is to animate whole pages - you can also target concrete elements to animate.
If the component appears as part of navigation, you can use Angular router animations.
While router animations are often used for full-page transitions, you can also target specific elements inside the page. Caveat of this solution is that you whole page must be animated, you cannot animate destruction of a component if his parent no longer exists.
Stack blitz example.
@keyframes appear {
0% {
transform: scale(0);
}
100% {
transform: scale(1);
}
}
@keyframes disappear {
0% {
transform: scale(1);
}
100% {
transform: scale(0);
}
}
:root {
&::view-transition-new(animatedElement) {
animation-name: appear;
animation-duration: 1s;
}
&::view-transition-old(animatedElement) {
animation-name: disappear;
animation-duration: 1s;
}
}
2. When the component is shown/hidden with *ngIf (or similar)
Given described problem you want to add and remove classes from the component. What we can do is create a custom directive, very similar to how ngIf operates, that would delay element destruction for the duration of the animation, here is a stack blitz example.
In your case, the goal is to toggle CSS classes on the component when it appears or disappears. One way to achieve this is by creating a custom structural directive—similar to *ngIf—that delays the element’s removal from the DOM long enough for the exit animation to complete. This ensures the animation plays fully before the element is destroyed. Here’s a stack blitz example.
@Directive({
selector: '[appAnimateDirective]',
})
export class AnimateDirective {
private renderer = inject(Renderer2);
private viewContainerRef = inject(ViewContainerRef);
private templateRef = inject(TemplateRef);
private declare elementRef: HTMLElement;
isShow = input.required<boolean>({ alias: 'appAnimateDirective' });
animationDuration = input.required<number>({
alias: 'appAnimateDirectiveAnimationDuration',
});
classOnHide = input.required<string>({
alias: 'appAnimateDirectiveClassOnHide',
});
constructor() {
this.handleContentToggleEffect();
}
private handleContentToggleEffect(): void {
effect(() => {
if (this.isShow()) this.addComponent();
else if (!this.isShow()) {
this.addHideClass();
setTimeout(() => {
this.removeComponent();
}, this.animationDuration());
}
});
}
private addComponent(): void {
this.viewContainerRef.clear();
const template = this.viewContainerRef.createEmbeddedView(this.templateRef);
this.elementRef = template.rootNodes[0] as HTMLElement;
}
private removeComponent(): void {
this.viewContainerRef.clear();
}
private addHideClass(): void {
this.renderer.addClass(this.elementRef, this.classOnHide());
}
}
This is just a proof of concept, there are several way of improving this tool, such as:
Pass animation duration via CSS variable from TS to avoid duplication
Cancel pending timeouts if the state changes mid-animation
Make it reusable with different animation types
A bit different perspective on animating *ngif / @if can be found here.
:leaveis done.