Animating Angular’s *ngIf and *ngFor

Jared Youtsey | ng-conf | May 2019

*ngIf and *ngFor will remove elements from the DOM. There isn’t a CSS solution for animating a non-existing element in the DOM. But Angular provides us with a simple solution.

For purposes of brevity, wherever I refer to *ngIf it is equally applicable to *ngFor. The complete, working code can be downloaded here.

Let’s start with the default application generated by the CLI and just modify the nice Angular logo in and out of the view based on a button we add.

ng new ngifAnimation

You won’t need routing and can select SCSS for the styling.

Let’s add the button we want to use to toggle our *ngIf on the image. Open app.component.html and add a simple button: (this is the default HTML)

<!--The content below is only a placeholder and can be replaced.--
<div style="text-align:center">
<h1>Welcome to {{ title }}!</h1>
<button (click)="onClick()">Toggle Image</button>
...

Now let’s add the onClick() method to the class that toggles a public variable showImage:

export class AppComponent {
  title = 'ngifAnimation';  
  showImage = false;
  onClick() {
    this.showImage = !this.showImage;
  }
}

Now, let’s add the *ngIf in the template on the <img> tag:

<img
  *ngIf="showImage"
  width="300"
  alt="Angular Logo"
  src="..."
/>

Let’s add a little bit of CSS to force the button to stay put when the image pops in and out: (app.component.scss)

button {
  display: block;
  margin: 0 auto;
  clear: both;
}

If you run the app now you’ll be able to click the button and the image will jarringly pop in and out of the view. If you check your developer tools, you’ll find that the <img> tag is popping in and out of the DOM. When showImage is false the <img> tag isn’t even present. This is where our inability to use CSS comes into play. It’s a terrible user experience to have elements, especially large ones, pop in and out without some transition. Let’s make it grow and fade in and out in a pleasing manner!

To handle animations (for way more reasons than just the one covered in this article) Angular provides the BrowserAnimationsModule. As of the latest Visual Studio Code, though, it doesn’t want to auto-import this module for you if you add it to your AppModule imports. It’s hidden in @angular/platform-browser/animations. Let’s add the import manually and add it to the module’s imports.

import { BrowserModule } from '@angular/platform-browser';
import { NgModule } from '@angular/core';
import { AppComponent } from './app.component';
import { BrowserAnimationsModule } from '@angular/platform-browser/animations';
@NgModule({
  declarations: [AppComponent],
  imports: [BrowserModule, BrowserAnimationsModule],
  providers: [],
  bootstrap: [AppComponent]
})
export class AppModule {}

Now we’re ready to add our Angular animations! But where? We’ll approach this in the simplest manner. But be aware that we’re just scratching the surface of the power of Angular animation. It’s worth learning much more about. The simple approach is directly in the affected component. In our case, that’s app.component.ts's @Component directive. Here is the whole thing, but don’t worry, we’ll break it down and explain it.

import { trigger, state, style, animate, transition } from '@angular/animations';
@Component({
  ...,
  animations: [
    trigger(
      'inOutAnimation', 
      [
        transition(
          ':enter', 
          [
            style({ height: 0, opacity: 0 }),
            animate('1s ease-out', 
                    style({ height: 300, opacity: 1 }))
          ]
        ),
        transition(
          ':leave', 
          [
            style({ height: 300, opacity: 1 }),
            animate('1s ease-in', 
                    style({ height: 0, opacity: 0 }))
          ]
        )
      ]
    )
  ]
})

Whew! That’s a lot and it’s not terribly obvious without reading through it carefully. Let’s break it down, bit by bit.

First, animations: [] is an array of things we want to happen or state definitions. In this case we just want to trigger an animation called inOutAnimation. You can name this what you like. It should be descriptive for what it accomplishes or what it should consistently apply to. In our case we are animating an image in and out of the view.

Then, we give the trigger a set of states and/or transitions. We only need two specific transitions to occur that are related to *ngIf: :enter and :leave. These are the states that CSS just doesn’t give us. :enter is when a DOM element is being added, and :leave is when a DOM element is being removed.

When we want the image to :enter we are starting with the style of height: 0, opacity: 0. It’s basically invisible to start with. When it’s done we would like it to be 300 pixels tall and be completely opaque.

This is where the animate instruction comes in. We are going to animate over 1) a period of time 2) with a particular easing mechanism 3) to a new style. 1 and 2 are combined in the first string-based instruction, 0.3s ease-out. This means that we are animating to the new style over 0.3 seconds, and we are easing out, or coming to a gentle stop rather than a sudden one. 3 specifies what the end styling should be. In our case that’s 300 pixels high and completely opaque.

If you run this now you’ll find that nothing has changed. We now need to apply the animation to the element that is being added/removed from the DOM. In this case, it’s our <img> tag that has the *ngIf directive on it.

<img
  *ngIf="showImage"
  [@inOutAnimation]
  width="300"
  alt="Angular Logo"
  src="..."
/>

Here we use the name of the trigger to bind the animation to the template element.

If you run it now, you can click the button and the image zoom/fades in. Click it again and it’ll shrink/fade out! Voila!

Personally, I find the syntax of Angular animations somewhat difficult. It’s non-obvious and, if you’re not doing it every day, you’re probably going to have to re-learn this a few times. And the template syntax works with or without the []'s, which makes me scratch my head a bit.

Maybe the Angular team will give us a ReactiveAnimationsModule someday that makes animation a bit easier to work with, like ReactiveFormsModule did for forms? One can hope.

This is just scratching the surface of what Angular animations are capable of. Very complex transforms/transitions are possible and can be carefully coordinated in ways that CSS just can’t guarantee.

As a side note, if you’re worried about performance vs pure CSS animations, this is a quote from the Angular docs:

Angular’s animation system lets you build animations that run with the same kind of native performance found in pure CSS animations. You can also tightly integrate your animation logic with the rest of your application code, for ease of control.

If you’ve found this useful, I’d appreciate a few claps for this article.

If you’d like to learn more about Angular in a fun environment while hanging out with the movers and shakers of the Angular world, snag a ticket to ng-conf and join us for the best Angular conference in the US.

Image by PIRO4D from Pixabay.

For more Angular goodness, be sure to check out the latest episode of The Angular Show podcast.

ng-conf: Join us for the Reliable Web Summit

Come learn from community members and leaders the best ways to build reliable web applications, write quality code, choose scalable architectures, and create effective automated tests. Powered by ng-conf, join us for the Reliable Web Summit this August 26th & 27th, 2021.
https://reliablewebsummit.com/

23