9

With Angular Animations it's possible to add transitions/ animations to elements and their children when they enter or leave the view, like this:

@Component({
  animations: [
    trigger('containerTrigger', [
      transition(':enter', [
        style({ opacity: 0, transform: 'translateY(-10px)' }),
        animate('250ms 100ms', style({ opacity: 1, transform: 'none' })),
      ]),
    ]),
  ]
})

While this works fine, it gets complex very quickly when adding more complex transitons/ animations not only to the selected element but also its children.

Also for someone who is mainly maintining CSS in a project, it might be hard to get used to Angular's animation syntax. Then it's easier to read and maintain a CSS like this:

.container {
  opacity: 0;
  
  &.is-active {
    opacity: 1;
    transition: all 0.2s linear;
  }
}

.container__heading {
  transform: translateX(-10px);

  .container.is-active & {
      transform: none;
      transition: all 0.2s linear;
  }
}

The question: is it possible to add a CSS class on the :enter and :leave events instead of running an Angular Animation?

3
  • Do you want to attach the CSS class to an element and all its children? Or only a specific element? Commented Sep 22, 2020 at 12:38
  • The idea is to attach a CSS class to one or multiple elements. The transitions on children can be target with child selectors. This would be very easy to maintain like in the example CSS snippet. But maybe it would only work if every element gets a class, that has a transition. Somehow Angular need to know, when :leave is done. Commented Sep 22, 2020 at 13:16
  • Possible duplicate of How to give class name to animation state in angular 2/4?. Commented Sep 23, 2020 at 7:08

2 Answers 2

1
+50

Unfortunatly you cant add or remove classes with angulars animation API (see How to give class name to animation state in angular 2/4?).

Binding with e.g. [ngClass] will also not work in this scenario, because when the :enter animation triggers, the element is not in the actual DOM yet, so any applied css will have no impact. https://angular.io/guide/transition-and-triggers#enter-and-leave-aliases

Sign up to request clarification or add additional context in comments.

1 Comment

Thanks for finding this duplicate. I searched before posting my question but didn't came up with any result. Too bad though, that it's not possible.
0

--- 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.

Comments

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.