10

I have a container that expands/shrinks. There is an element inside that container that should fade in when the container expands and fade out when the container shrinks.

My Problem

  • When the container expands, the animation of both elements work.
  • When the container shrinks, only the container animation works.
  • If I remove the container expansion animation, then the fade in/out animations work as expected.

How do I make all animations execute in parallel under both expand/shrink conditions?

Note the use of ngIf. This is intentional as it destroys the element at the end of the animation sequence.

Here is a plunkr of my current state: https://embed.plnkr.co/TXYoGf9QpErWmlRvQF9Z/

The component class:

export class App {
  expanded = true;

  toggleExpandedState() {
    this.expanded = !this.expanded;
  }

  constructor() {
  }
}

The template:

<div class="container" [@expansionTrigger]="expanded">
  <div class="constant-item"></div>
  <div class="fade-item" [@stateAnimation] *ngIf="expanded"></div>
</div>

<button (click)="toggleExpandedState()">Toggle Fade</button>

and the component animation:

  trigger('expansionTrigger', [
    state('1', style({
      width: '250px'
    })),
    state('0', style({
      width: '160px'
    })),
    transition('0 => 1', animate('200ms ease-in')),
    transition('1 => 0', animate('200ms ease-out'))
  ]),
  trigger('stateAnimation', [
    transition(':enter', [
      style({
        opacity: 0
      }),
      animate('200ms 350ms ease-in', style({
        opacity: 1
      }))
    ]),
    transition(':leave', [
      style({
        opacity: 1
      })
      animate('1s', style({
        opacity: 0
      }))
    ])
  ])
Sangwin Gawande
  • 7,658
  • 8
  • 48
  • 66
AJ X.
  • 2,729
  • 1
  • 23
  • 33

2 Answers2

3

In hopes to save others the frustration, it looks like it is a bug with Angular 4.

There are currently two open issues on Github that appear to be pretty closely related:

https://github.com/angular/angular/issues/15798

https://github.com/angular/angular/issues/15753

Update

According to the github issue, the animation bug won't be fixed to work as it once did in Angular2. Instead query() and animateChild() are being introduced. The new animation features are available as of 4.2.0-rc2.

In summary:

Component Definition

@Component({
  selector: 'my-app',
  template: `
    <div>
      <h2>Hello {{name}}</h2>
      <button (click)="toggle()">Toggle</button>
      <div *ngIf="show" @ngIfAnimation>
        <h3 @easeInOut>I'm inside ngIf</h3>
      </div>
    </div>
  `,
animations: [
  trigger('ngIfAnimation', [
    transition(':enter, :leave', [
      query('@*', animateChild())
    ])
  ])
  trigger('easeInOut', [
    transition('void => *', [
        style({
            opacity: 0
        }),
        animate("1s ease-in-out", style({
            opacity: 1
        }))
    ]),
    transition('* => void', [
        style({
            opacity: 1
        }),
        animate("1s ease-in-out", style({
            opacity: 0
        }))
    ])
  ])
]]

})
export class App {
  name:string;
  show:boolean = false;

  constructor() {
    this.name = `Angular! v${VERSION.full}`
  }

  toggle() {
    this.show = !this.show;
  }
}

There is a working example available here: https://embed.plnkr.co/RJgAunJfibBKNFt0DCZS/

AJ X.
  • 2,729
  • 1
  • 23
  • 33
2

here is what you'll need to do:

plnkr here: https://plnkr.co/edit/AQEVh3GGc31ivQZMp223?p=preview

remove *ngIf="expanded" use [@stateAnimation]="expanded"

replace your stateAnimate trigger with this:

trigger('stateAnimation', [
          state('1', style({ opacity: 0 })),
          state('0', style({opacity: 1})),
          transition('0 => 1', animate('200ms ease-in')),
          transition('1 => 0', animate('200ms ease-out'))
        ])

here is the full code:

//our root app component
import {Component, NgModule, VERSION, 
  Output,
  trigger,
  state,
  style,
  transition,
  animate,
} from '@angular/core'
import {BrowserModule} from '@angular/platform-browser'
import { BrowserAnimationsModule } from '@angular/platform-browser/animations';

@Component({
  selector: 'my-app',
  template: `
    <div class="container" [@expansionTrigger]="expanded">
      <div class="constant-item"></div>
      <div class="fade-item" [@stateAnimation]="expanded"></div>
    </div>

    <button (click)="toggleExpandedState()">Toggle Fade</button>
  `,
  animations: [
    trigger('expansionTrigger', [
      state('1', style({ width: '250px' })),
      state('0', style({width:'160px'})),
      transition('0 => 1', animate('200ms ease-in')),
      transition('1 => 0', animate('200ms ease-out'))
    ]),
    trigger('stateAnimation', [
      state('1', style({ opacity: 0 })),
      state('0', style({opacity: 1})),
      transition('0 => 1', animate('200ms ease-in')),
      transition('1 => 0', animate('200ms ease-out'))
    ])
  ]
})
export class App {
  expanded = true;

  toggleExpandedState() {
    this.expanded = !this.expanded;
  }

  constructor() {
  }
}

@NgModule({
  imports: [ BrowserModule, BrowserAnimationsModule ],
  declarations: [ App ],
  bootstrap: [ App ]
})
export class AppModule {}
Ahmed Musallam
  • 9,523
  • 4
  • 27
  • 47
  • Hi Ahmed -- thanks for your answer. Unfortunately that method doesn't destroy the the 'fade-item' element. This is what is creating the challenge here. – AJ X. Apr 10 '17 at 01:31
  • So you want the fade-item to be destroyed? You never mentioned this in your question.. please update your question with more specifics on what you're expecting. – Ahmed Musallam Apr 10 '17 at 01:33
  • Updated. Thanks. Along those lines, I'm looking for an Angular2 solution. CSS hackery to emulate the element being destroyed won't work. – AJ X. Apr 10 '17 at 01:47
  • @axlj I'm really curious why you want the element destroyed? Anyway, you might have some luck with the code above and Animation callbacks `.start` and `.done` described here: https://angular.io/docs/ts/latest/guide/animations.html#!#animation-callbacks. That with some component change detection listener for `AfterViewChecked` to perform the animation right after the `fade-item`'s constructed or conversely right before its destroyed. – Ahmed Musallam Apr 12 '17 at 20:25
  • Thanks again for your help. I'll take a look at the start/done events.. This question is an extremely simplified use case for what I'm trying to achieve. I'm porting a multimedia desktop app to the web. Destroying the elements solves a bunch of issues of which are primarily (1) minimizing resource requirements and (2) avoiding extraneous CSS to achieve the same UX. – AJ X. Apr 12 '17 at 21:33