How to animate the :host element on :leave event using animations in Angular 12?

1.5k views Asked by At

We have a very simple component that should be faded in and out on :enter and :leave events:

import { Component, HostBinding } from '@angular/core';
import { animate, style, transition, trigger } from '@angular/animations';

@Component({
  selector: 'app-test',
  templateUrl: './test.component.html',
  styleUrls: ['./test.component.scss'],
  animations: [
    trigger('host', [
      transition(':enter', [
        style({ opacity: 0 }),
        animate('240ms linear', style({ opacity: 1 })),
      ]),
      transition(':leave', [
        style({ opacity: 1 }),
        animate('240ms linear', style({ opacity: 0 })),
      ])
    ]),
  ]
})

export class TestComponent {
  @HostBinding('@host') host() {
    return true;
  }
}

The component is used like this:

<app-test *ngIf="someBoolean"></app-test>

Now the animation on :enter does run. But the element is not faded out, when the component is being removed (the variable someBoolean becomes false).

What is missing?

2

There are 2 answers

2
Owen Kelvin On

Using *ngIf in the parent component makes the element animations not to be registered

Lets follow what happens

  • someBoolean => starting with false
  • <app-test *ngIf='someBoolean'></app-test> does not exist
  • someBoolean => true
  • Load <app-test></app-test>
  • Load animations; ... Wait a minute, what just happened? We loaded app-test then loaded the animations! But at what point is animations to be triggered? The answer to this question is why your animations are not being triggered. We need to reqister the animations before they can be applied

So Lets stop the above sequence of events and retry the steps

  • someBoolean => starting with false
  • <app-test [shown]='someBoolean'></app-test> created
  • Register Animations
  • someBoolean => true
  • Display contents of <app-test></app-test>
  • Animation kicks in as they have been registered!

Okay, now that we have an idea of how this would work lets get down to code

Component usage

`<app-test [shown]='someBoolean'></app-test>`

Component TS File

@Component({
  selector: "app-test",
  templateUrl: "./test.component.html",
  styleUrls: ["./test.component.css"],
  animations: [
    trigger("host", [
      transition(":enter", [
        style({ opacity: 0 }),
        animate("240ms linear", style({ opacity: 1 }))
      ]),
      transition(":leave", [
        style({ opacity: 1 }),
        animate("240ms linear", style({ opacity: 0 }))
      ])
    ])
  ]
})
export class TestComponent implements OnInit {
  // @HostBinding("@host") host() {
  //   return true;
  // }
  @Input() shown;

  constructor() {}

  ngOnInit() {}
}

I have commented out @HostBinding as I didn't see how it is implemented here.

Finally we can apply *ngIf in the contents of the component

<div *ngIf='shown'>
  <!-- Animation works! -->
</div>

See Demo Here

0
David Lopes On

Does this solve the issue?

import {
  animate,
  state,
  style,
  transition,
  trigger,
} from '@angular/animations';
import { Component } from '@angular/core';

@Component({
  selector: 'parent',
  animations: [
    trigger('toggle', [
      state('true', style({ overflow: 'hidden', opacity: '*' })),
      state('false', style({ overflow: 'hidden', opacity: 0, height: 0 })),
      transition('true => false', animate('400ms ease-in-out')),
      transition('false => true', animate('400ms ease-in-out')),
    ]),
  ],
  template: `
    <button (click)="someBoolean = !someBoolean">CLICK</button>
    <div [@toggle]="someBoolean">
      <app-test></app-test>
    </div>
  `,
})
export class ParentComponent {
  someBoolean = false;
}