Useful Development

Angular: using animations with NgIf

posted on Aug 27, 2019

Adding or removing an element/component based on a condition with a structural directive such as NgIf is a easy to do with Angular. Animating the change is more challenging as when the element doesn't exist in the DOM a transition cannot be applied to it. If an animation is needed the common approach is to just hide the element. This isn't always ideal if you need to either reset the state of a component or it is performing intensive tasks which could carry on using resources whilst it is invisible to the user. This walk-through shows a way round the problem using animation state. The full code sample is available on StackBlitz. I'll only show the interesting parts here in the post.

fade in ngIf gif

To start create a new component called fade which uses ng-content to display dynamic content. This content is what will be faded in or out. The component has an input property called show. When set to true the component needs to add the content to the dom, in a hidden state and then transition the content to be visible. When set to false it will transition it to hidden (opacity 0) and then remove the content from the DOM.

fade.component.html

  
<div
  *ngIf="show"
  class="fade"
  [@state]="state"
  (@state.done)="animationDone($event)"
>
<ng-content #fadeableContent></ng-content>
</div>
 
 

The animation itself is just a basic transition between two states.

fade.component.ts

  
   export type FadeState = 'visible' | 'hidden';

    @Component({
      selector: 'app-fade',
      templateUrl: './fade.component.html',
      styleUrls: ['./fade.component.scss'],
      animations: [
        trigger('state', [
          state(
            'visible',
            style({
              opacity: '1'
            })
          ),
          state(
            'hidden',
            style({
              opacity: '0'
            })
          ),
          transition('* => visible', [animate('500ms ease-out')]),
          transition('visible => hidden', [animate('500ms ease-out')])
        ])
      ],
      changeDetection: ChangeDetectionStrategy.OnPush
    })
  
 

fade.component.css

  
    :host {
      display: block;
    }
    .fade {
      opacity: 0;
    }
  
 

The trick to achieving this is to use a setter so that both tasks can be carried out from a single property change. If show is set to true also set the state to visible to trigger the fade in animation. If show is set to false trigger the fade out animation by setting the state to hidden and then in the animation state done callback set show to false to remove it.

fade.component.ts

  
    export class FadeComponent {
      state: FadeState;
      // tslint:disable-next-line: variable-name
      private _show: boolean;
      get show() {
        return this._show;
      }
      @Input()
      set show(value: boolean) {
        if (value) {
          // show the content and set it's state to trigger fade in animation
          this._show = value;
          this.state = 'visible';
        } else {
          // just trigger the fade out animation
          this.state = 'hidden';
        }
      }

      animationDone(event: AnimationEvent) {
        // now remove the 
        if (event.fromState === 'visible' && event.toState === 'hidden') {
          this._show = false;
        }
      }
    }
  
 

That's pretty much it. In this sample we created a reusable wrapper component but you could apply the same approach to individual elements or components.

typescript
angular