GSAP Animations in Angular - Handle animateOut
Tags: tutorial, angular, typescript, greensock
Published: 2020-06-17 14:53:41

# Handle the out animation of our components

Now that we have done the **animationIn** of our components, we want to handle also the **animationOut**.

For example, we want to *hide* our HTML element with a fancy fade out animation, but if we use the structural directive *ngIf of angular the animation will not work, because angular will remove physically the element from the view.

So how can we do it? Let's start with the classic *ngIf directive.

## Toggle HTML elements

Let's proceed with the logic to toggle our HTML elements, in our **app.component.ts** we need to add this code:

```typescript
export class AppComponent {
  title = 'my-app-title';
  showP = true;
  toggleP() {
    this.showP = !this.showP;
  }
}
```

The method *toggleP* will toggle the boolean *showP*, which will be used in our **app.component.html** to show/hide our elements:

```html
<p
  fadeInAnimation
  [duration]="1"
  [delay]="1"
  *ngIf="showP"
>
  That's a cool effect, or not?
</p>
<p
  fadeInAnimation
  [duration]="1"
  [delay]="2"
  *ngIf="showP"
>
  This too, but a little bit delayed
</p>

```

Let's add a button to call *toggleP* method (animated too 😎):

```html
<p
  fadeInAnimation
  [duration]="1"
  [delay]="1"
  *ngIf="showP"
>
  That's a cool effect, or not?
</p>
<p
  fadeInAnimation
  [duration]="1"
  [delay]="2"
  *ngIf="showP"
>
  This too, but a little bit delayed
</p>
<button
  fadeInAnimation
  [duration]="1"
  [delay]="3"
  (click)="toggleP()"
>{{showP ? "Hide P" : "Show P"}}
</button>
```

And run the application, as you will see the animationOut will not be triggered:

![ngIf fail](https://dev-to-uploads.s3.amazonaws.com/i/78yhw26di4jewxfos2iv.gif)

As you can see the directive removes directly the HTML element, so how can we handle it without deleting the HTML element?

## The ***ngIfAnimated*** directive

We can create a custom **structural directive**, create a directory inside `directives/` folder, called *structural*, and a file named `ngIf-animated.directive.ts`:

```typescript
import {Directive, ElementRef, EmbeddedViewRef, Input, TemplateRef, ViewContainerRef} from '@angular/core';
import {CoreAnimationDirective} from '../gsap/core-animation.directive';

@Directive({
  selector: '[ngIfAnimated]'
})
export class NgIfAnimatedDirective {
  childViewRef: EmbeddedViewRef<CoreAnimationDirective> = null;

  constructor(
    private element: ElementRef,
    private templateRef: TemplateRef<any>,
    private viewContainer: ViewContainerRef
  ) {}

  @Input()
  set ngIfAnimated(show) {
    if(show) {
      this.childViewRef = this.viewContainer.createEmbeddedView(this.templateRef);
    } else {
      if(this.childViewRef) {
        const node = this.childViewRef.rootNodes[0];
        if(node) {
          node.dispatchEvent(new CustomEvent('animate-out', {detail: {parentViewRef: this.viewContainer}}));
        }
      }
    }
  }
}
```

This directive will show and hide an embedded **viewRef** using an @Input parameter called **ngIfAnimated**.

If passed ***show*** boolean is true, then it will create and **embeddedView**, else it will dispatch a **CustomEvent** on the first *rootNode*, passing the **viewContainer** reference. We will see why in a moment.

We need to handle the event trigger inside our ***CoreAnimationDirective***, the component will receive the event, run the animation out and clear the parent view:

```typescript
export class CoreAnimationDirective {
  @Input() duration = 1;
  @Input() delay = 0;

  @Output() complete: EventEmitter<null> = new EventEmitter();
  @Output() reverseComplete: EventEmitter<null> = new EventEmitter();
  protected timeline: TimelineMax;

  constructor(protected element: ElementRef) {
    // handle animate-out event
    this.element.nativeElement.addEventListener('animate-out', ({detail}) => {
      this.animateOut(detail.parentViewRef);
    })
    this.timeline = new TimelineMax({
      onComplete: _ => this.complete.emit(),
      onReverseComplete: _ => this.reverseComplete.emit(),
      paused:true,
      reversed:true
    });
  }

  protected animateIn() {
    if(this.timeline.isActive()) {
      this.timeline.kill();
    }
    this.timeline.play();
  }

  protected animateOut(parentViewRef: ViewContainerRef) {
    if(this.timeline.isActive()) {
      this.timeline.kill();
    }
    setTimeout(() => {
      this.timeline.timeScale(this.duration).delay(0).reverse();
      setTimeout(_ => {
        if (parentViewRef) {
          parentViewRef.clear();
        }
      }, this.duration * 1000);
    }, this.delay * 1000);
  }
}
```

As you can see we have added 2 new thing to our CoreAnimationDirective:

1. ***animate-out*** event handler - handle the event on HTML element and call the method **animateOut** 

2. ***animateOut*** method - this method play the animation of the directive, but *reversed*. It kills the timeline if is running, and set a timeout to clear the *parentViewRef*, according to the animation duration and delay.

Now we have only to declare our **ngIfAnimated** inside the *app.module* and replace the previous *ngIf with *ngIfAnimated on our html elements:

```typescript
@NgModule({
  declarations: [
    AppComponent,
    FadeInAnimationDirective,
    NgIfAnimatedDirective
  ],
  imports: [
    BrowserModule,
    AppRoutingModule
  ],
  providers: [],
  bootstrap: [AppComponent]
})
export class AppModule { }
```
 
```html
<p
  fadeInAnimation
  [duration]="1"
  [delay]="1"
  *ngIfAnimated="showP"
>
  That's a cool effect, or not?
</p>
<p
  fadeInAnimation
  [duration]="1"
  [delay]="2"
  *ngIfAnimated="showP"
>
  This too, but a little bit delayed
</p>
<button
  fadeInAnimation
  [duration]="1"
  [delay]="3"
  (click)="toggleP()"
>{{showP ? "Hide P" : "Show P"}}
</button>
```

The result should look like this:

![Animate out result](https://dev-to-uploads.s3.amazonaws.com/i/n5vm7u7h70odg88oxjs4.gif)

## What will come next

In the next parts of this series we will take a look on how to:

1. Handle animations with ***MouseEvent***, for example to scale up a button.
2. How to keep all ***stateless***, using ngrx, to handle complex animations while routing.