Skip to main content
Ben Nadel at InVision In Real Life (IRL) 2018 (Hollywood, CA) with: Lindsey Redinger
Ben Nadel at InVision In Real Life (IRL) 2018 (Hollywood, CA) with: Lindsey Redinger

Using No-Op Transitions To Prevent Animation During The Initial Render Of ngFor In Angular 5.2.6

By
Published in Comments (7)

When Angular 2 first came out, the Animations module didn't block nested transitions (like it did in AngularJS 1.2). So, we had to jump through a bunch of hoops to try and have animation "state" drive the blocking. As of Angular 4.2, however, the revamped Animations module will block nested animations by default. We can now leverage this new behavior to block the initial rendering of ngFor item-animations by adding a no-op (No Operation) transition to one of the parent elements. Just like we did with ngRepeat in Angular 1.2.

Run this demo in my JavaScript Demos project on GitHub.

Animating new items onto the screen can lead to a very pleasing, very organic user experience (UX). However, when it comes to animating lists, we almost never want to animate the initial rendering of the list - only the new items that are added after the list has been mounted in the DOM (Document Object Model). To block the initial rendering of the ngFor items, we can animate the parent element during the same state-transition; this will naturally block the nested animations. This parent animation can be meaningful; or, it can be a no-op transition:

transition( ":enter", [] )

This no-op transition has no duration and defines no style changes. But, it is sufficient to flag the associated element as being "in transition"; which, in turn, is enough to block the ":enter" animations of its descendants.

To see this in action, I've created a small demo in which I have two lists of friends. In each list, new friend LI elements are transitioned onto the screen. However, one of the lists adds a no-op transition on the parent UL element in order to block the initial render animation of the nested LIs:

// Import the core angular services.
import { animate } from "@angular/animations";
import { Component } from "@angular/core";
import { style } from "@angular/animations";
import { transition } from "@angular/animations";
import { trigger } from "@angular/animations";

// ----------------------------------------------------------------------------------- //
// ----------------------------------------------------------------------------------- //

@Component({
	selector: "my-app",
	animations: [
		trigger(
			"friend",
			[
				transition(
					":enter",
					[
						style({
							opacity: 0,
							transform: "translateX( 300px )"
						}),
						animate(
							1000,
							style({
								opacity: 1,
								transform: "translateX( 0px )"
							})
						)
					]
				)
			]
		),
		// By default, the "parent" animation in a nested-animation context will take
		// precedence, blocking the child animation. As such, if we want to block a
		// given animation, we simply have to create a no-op (No Operation) transition
		// above it in the DOM (Document Object Model). In this case, by defining a no-op
		// ":enter" transition on the UL element, we block the INITIAL ":enter"
		// transitions of the LI elements contained within it. But, ONLY WHEN THE UL IS
		// TRANSITION (ie, being ":enter"d). Once the parent UL is mounted, subsequent
		// LI ":enter" animations are allowed to run.
		trigger(
			"blockInitialRenderAnimation",
			[
				transition( ":enter", [] )
			]
		)
	],
	styleUrls: [ "./app.component.less" ],
	template:
	`
		<form (submit)="$event.preventDefault(); processForm()">
			<input #input type="text" [value]="form.name" (input)="form.name = input.value" />
			<button type="submit">Add Friend</button>
		</form>

		<p>
			<a (click)="toggleLists()">Toggle Lists of Friends</a>
		</p>

		<div *ngIf="isShowingLists">

			<h2>
				With Initial Animation Blocking
			</h2>

			<ul @blockInitialRenderAnimation>
				<li *ngFor="let friend of friends" @friend>
					{{ friend }}
				</li>
			</ul>

		</div>

		<div *ngIf="isShowingLists">

			<h2>
				Without Initial Animation Blocking
			</h2>

			<ul>
				<li *ngFor="let friend of friends" @friend>
					{{ friend }}
				</li>
			</ul>

		</div>
	`
})
export class AppComponent {

	public form: {
		name: string;
	};
	public friends: string[];
	public isShowingLists: boolean;

	// I initialize the app component.
	constructor() {

		this.form = {
			name: ""
		};
		this.friends = [ "Sarah", "Kim", "Tricia" ];
		this.isShowingLists = true;

	}

	// ---
	// PUBLIC METHODS.
	// ---

	// I process the new friend form.
	public processForm() : void {

		if ( this.form.name ) {

			this.friends.push( this.form.name );
			this.form.name = "";

		}

	}


	// I toggle the visibility of the friend lists.
	public toggleLists() : void {

		this.isShowingLists = ! this.isShowingLists;

	}

}

As you can see, one of the UL elements has the @blockInitialRenderAnimation animation trigger and the other does not. And, when we load this page for the first time, we can see that only the LI elements in the second list demonstrate ":enter" transitions:

Blocking the initial rendering of ngFor item animations in Angular 5.2.6.

As you can see, the LI elements in the first list render instantly during first-load whereas the LI elements in the second list transition onto the screen during first-load. The first list provides a much nicer user experience. And, it doesn't prevent the subsequent friends from transition into place. Once the lists are rendered, if we add a new friend, we get the following output:

Blocking the initial rendering of ngFor item animations in Angular 5.2.6, while still alowing subsequent animations.

As you can see, both lists are still capable of animating the ":enter" transition on LI elements once the parent UL has been fully rendered on the page.

Animations and transitions are powerful tools in the user experience (UX) toolbox. But, we have to be careful not to use them at the wrong time or in the wrong place or we run the risk of providing an even worse experience for our users. Thankfully, in Angular 5.2.6, we can easily leverage the automatic blocking of nested transitions in order to block the initial rendering of an ngFor loop while still allowing for new ngFor loop items to be transitioned into place.

Want to use code from this post? Check out the license.

Reader Comments

447 Comments

Ben. Another great tutorial.

Three questions.

Firstly. So, because, 'UL' is a parent in the DOM to 'LI', it takes precedence in the animation order?

Secondly. Does the order of the trigger blocks make a difference? Would the same result have been achieved, if you had written the 'friend' trigger block second?

And, finally, how would you go about animating the entire list after adding a friend, but without it animating after the initial page load?

15,841 Comments

@Charles,

Yes, because the UL is a parent, it takes precedence on the animation, blocking nested animations by default. You can, however, choose to allow any child / nested animations to run by using the "animateChild()" method (not shown in this demo) within the transition definition.

As far as the order of the trigger() blocks, I don't believe the order matters. However, I *do believe* the order of the transition() blocks within a trigger() matters. I think that Angular will use the first transition() block that matches the state-change, and then stop looking for transition definitions.

Regarding animating the entire list after a friend is added, I am not sure that I have a good use-case for that in mind. Perhaps you could use a specific state-transition on the parent to block the initial load, and then a different state to allow subsequent changes to cause animations. I'd have to noodle on that some more.

447 Comments

Thanks Ben, for the explanation. Makes sense. I am exploring the Angular [Google] Material module at the moment, which has some cool built in animation effects, although I am just weighing up whether I would actually create an entire UI, using this module. Looks a little too clinical for my liking, although it does implement an extremely modular approach to UI building.

https://material.angular.io/components/icon/overview

15,841 Comments

@Charles,

Google Material is one of those things I want to look into "eventually." I'm particularly interested in how they approach "themeing", as this topic completely eludes me. I love the simulated-encapsulation of the styled components. But, I have zero understanding of how that can be made to be flexible from a cross-cutting concerns kind of way. I'd be very curious to see how the Material Design library handles it.

One day .... :D

447 Comments

Well, I have only use a couple of Material elements like snackbar. Although, I couldn't get the tooltip to work, because my CLI is not up to date enough. I did watch a tutorial on Material, and it looks like you can create UI quite quickly in Angular 6. Plus, the new Tree element looks very cool. It allows you to create UI directory trees!

The question for me, is do I want my UI to look like Google. I am in 2 minds on this one. Material has some lovely elements but I sometimes think that Google UI looks too clinical & impersonal, so I may just add elements here & there, rather than create an entire Material driven UI! But, I guess this is all very subjective.

But rapid development is definitely one of Material's plus points.

1 Comments

Thanks Ben! This saved me a lot of time at the expense of you taking your time to share this! Keep up the great work!

I believe in love. I believe in compassion. I believe in human rights. I believe that we can afford to give more of these gifts to the world around us because it costs us nothing to be decent and kind and understanding. And, I want you to know that when you land on this site, you are accepted for who you are, no matter how you identify, what truths you live, or whatever kind of goofy shit makes you feel alive! Rock on with your bad self!
Ben Nadel