Skip to main content
Ben Nadel at InVision In Real Life (IRL) 2019 (Phoenix, AZ) with: Lindsey Redinger
Ben Nadel at InVision In Real Life (IRL) 2019 (Phoenix, AZ) with: Lindsey Redinger ( @_lindseytron )

Possible Bug With Asymmetric Support For Nested Animations In Angular 2 RC 6

By on

Yesterday, I wrote about a possible bug with nested animations in Angular 2 RC 6. In that post, I demonstrated that Document Object Model (DOM) elements won't get destroyed if there are too many nested animations. As I continued to play around with this code, however, I think I realized that there was perhaps even more buggy behavior at hand - though this one is certainly much more open to interpretation. I believe there might be a bug in the animation functionality that expresses itself as asymmetric support for nested animations.

Run this demo in my JavaScript Demos project on GitHub.

When I refer "asymmetric support" for nested animations, I am specifically referring to "Enter" and "Leave" animations. It appears that nested "Enter" animations (ie, "void => *") are supported while nested "Leave" animations (ie, "* => void") are not supported. Though, of course, this asymmetric support may actually be a side-effect of a different bug (also opinion) that nested animations aren't being blocked by default.

Since it is unclear to me what the expectation of animation support is, it becomes harder to articulate the buggy behavior. I can only say that something "feels" funky with the current support. I am not sure if perhaps there is one bug that is just creeping into various parts of my research; or, if these are different bugs; or, if they're not actually bugs at all and its just my expectation that requires adjustment?

It's hard to say.

If you watch the video, I'm able to articulate things a bit more clearly. But, the idea for this demo is that I have a Container div that has two Box divs inside of it. Both the container and the boxes have the following transitions defined:

  • void => *
  • * => void

This means that both the container and the boxes have "enter" and "leave" transitions defined. For the sake of the exploration, both the container and the boxes can be toggled independently.

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

@Component({
	selector: "my-app",
	animations: [
		// Notice that with the container, we are defining the Void and Non-Void states
		// and then defining a transition time between the two. This is exactly the same
		// thing we are doing with the boxOne animation as well.
		trigger(
			"containerAnimation",
			[
				state(
					"void",
					style({
						borderRadius: 150
					})
				),
				state(
					"*",
					style({
						borderRadius: 5
					})
				),
				transition(
					"void => * , * => void",
					animate( "1000ms ease-in-out" )
				)
			]
		),

		// As the container is rendered, Box One will come up from the bottom. Notice
		// that we are defining the animation in the same way that we are defining the
		// container animation - void state, non-void state, and then transition timing.
		trigger(
			"boxOneAnimation",
			[
				state(
					"void",
					style({
						top: "100%",
						opacity: 0.0,
						transform: "rotate( 1000deg )"
					})
				),
				state(
					"*",
					style({
						top: "50%",
						opacity: 1.0,
						transform: "rotate( 0deg )"
					})
				),
				transition(
					"void => * , * => void",
					animate( "1000ms ease-in-out" )
				)
			]
		),

		// As the container is rendered, Box Two will come down from the top. For this
		// box, we are attempting to define that animation styling as part of each
		// transition configuration (rather than relying on state-based styles).
		// --
		// NOTE: I'm using a different format here in an attempt to see if the mode of
		// configuration is in some way adding to the asymmetric support for animation.
		trigger(
			"boxTwoAnimation",
			[
				transition(
					"void => *",
					[
						style({
							top: "-10%",
							opacity: 0.0,
							transform: "rotate( 1000deg )"
						}),
						animate(
							"1000ms ease-in-out",
							style({
								top: "50%",
								opacity: 1.0,
								transform: "rotate( 0deg )"
							})
						)
					]
				),
				transition(
					"* => void",
					[
						style({
							top: "50%",
							opacity: 1.0,
							transform: "rotate( 0deg )"
						}),
						animate(
							"1000ms ease-in-out",
							style({
								top: "-10%",
								opacity: 0.0,
								transform: "rotate( 1000deg )"
							})
						)
					]
				)
			]
		)
	],
	template:
	`
		<p>
			<a (click)="toggleContainer()">Toggle Container</a>
			&mdash;
			<a (click)="toggleBoxes()">Toggle Boxes</a>
		</p>

		<div *ngIf="isShowingContainer" @containerAnimation class="container">

			<div *ngIf="isShowingBoxes" @boxOneAnimation class="box-one">
				Box One
			</div>

			<div *ngIf="isShowingBoxes" @boxTwoAnimation class="box-two">
				Box Two
			</div>

		</div>
	`
})
export class AppComponent {

	public isShowingBoxes: boolean;
	public isShowingContainer: boolean;


	// I initialize the component.
	constructor() {

		this.isShowingBoxes = true;
		this.isShowingContainer = false;

	}


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


	// I show or hide the boxes depending on the current state.
	public toggleBoxes() : void {

		this.isShowingBoxes = ! this.isShowingBoxes;

	}


	// I show or hide the container depending on the current state.
	public toggleContainer() : void {

		this.isShowingContainer = ! this.isShowingContainer;

		// If we're toggling the container into view, show the boxes as well.
		if ( this.isShowingContainer ) {

			this.isShowingBoxes = true;

		}

	}

}

As you can see, the container is animated through its border-radius while the boxes are animated through their position and opacity. When we run this code and toggle the container into view, we can see that both the container and the boxes follow their "enter" animations:

Asymmetric support for enter and leave animations - showing enter animation in Angular 2 RC 6.

However, when we toggle the container out of view, we can see that only the "leave" animation of the container is executed - the "leave" animations for the boxes are omitted:

Asymmetric support for enter and leave animations - showing leave animation in Angular 2 RC 6.

Now, I thought this might be due to the fact that the boxes are controlled by *ngIf. So, I tried removing the conditional on the boxes, replacing this:

<div *ngIf="isShowingBoxes" @boxOneAnimation class="box-one">

... with this:

<div @boxOneAnimation class="box-one">

In this case, the box animations are now attached to static elements. And still, we get asymmetric support (and incur the bug about DOM destruction). If a static element can be entered into the view, I think it follows that a static element should also be leaved from the view as well.

So, like I said, this is quite open to interpretation. I think something is going wrong somewhere; but, I'm not exactly sure what it is. I'm not sure if the asymmetric support is a problem; or, if this is just a symptom of something else. What I can say is that not having "ng-enter" and "ng-leave" CSS classes to hook into (as we did in Angular 1.x) means that the same kind of support for cascading and nesting must necessarily be supported by the animation meta-data. And, I'm not quite sure what the canonical way of doing that in Angular 2 RC 6 is just yet.

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

Reader Comments

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