Skip to content

Provide hostbinding for (animate.enter) and (animate.leave) events #63199

@ahimik

Description

@ahimik

Which @angular/* package(s) are relevant/related to the feature request?

animations

Description

It is currently not possible to handle animation lifecycle events like (animate.enter) or (animate.leave) on the host element using @HostBinding() or @HostListener().

Please provide a way to host-bind or host-listen a callback function for these animation events so that custom animations can be run without adding template event bindings everywhere.

Why is this useful?

With GSAP (or any third-party animation lib), I currently have to manually bind AnimationCallbackEvent handlers in the component template for every instance:

<div (animate.enter)="animateEnter($event)" (animate.leave)="animateLeave($event)">
   ...
</div>

and inside component:

...
  animateEnter(event: AnimationCallbackEvent) {
    gsap.effects.fadeIn(event.target).eventCallback('onComplete', () => event.animationComplete());
  }

  animateEnter(event: AnimationCallbackEvent) {
    gsap.effects.fadeOut(event.target).eventCallback('onComplete', () => event.animationComplete());
  }

This results in repetitive boilerplate.

Now imagine a simple AnimateLeaveGsapDirective:

@Directive({
  selector: '[animateLeaveGsap]'
})
export class AnimateLeaveGsapDirective implements OnInit {

  /** The name of GSAP effect to apply for animate.leave */
  readonly animateLeaveGsap = input.required<string>();

  @HostBinding('(animate.leave)')
  animateLeave: Function;
  // or
  @HostListener('animate.leave', ['$event'])
  onLeave(event: AnimationCallbackEvent) {
      ...
  }

  ngOnInit(): void {

    const effectName = this.animateLeaveGsap();
    const effect = gsap.effects[this.animateLeaveGsap()] as (targets: gsap.TweenTarget) => gsap.core.Tween;

    if (!effect) {
      console.warn('Error binding GSAP leave animation. Effect not found:', effectName);
      return;
    }

    this.animateLeave = (event: AnimationCallbackEvent) => {
      effect(event.target).eventCallback('onComplete', () => event.animationComplete());
    }
  }

}

Then templates become as simple as CSS-class-like bindings:

<div animateEnterGsap="fade-in" animateLeaveGsap="fade-out">
   ...
</div>

No more boilerplate.

Proposed solution

Allow directives/components to attach animation lifecycle handlers on the host via a decorator or host metadata, similar to DOM events:

@HostBinding('(animate.enter)')
animationCallbackFunction: (event: AnimationCallbackEvent) => void;

Alternatives considered

No good alternatives. The only current option is to bind (animate.enter) / (animate.leave) in every template where the component/directive is used, which scales poorly and scatters animation wiring across templates.

Metadata

Metadata

Type

No type

Projects

No projects

Relationships

None yet

Development

No branches or pull requests

Issue actions