angular2 ng2-bootstrap collapse animation workaround

AttitudeL picture AttitudeL · Feb 17, 2017 · Viewed 8.7k times · Source

I am aware the issue that currently the animation code in ng2-bootstrap is commented out due to the unavailability of angular2's directive animation support.

Therefore I created a workaround by using angular2's animate in my component.

animations: [
  trigger('slideMenu', [
    state('true', style({ height: '0px' })),
    state('false', style({ height: '*' })),
    transition('1 => 0', animate('200ms ease-in')),
    transition('0 => 1', animate('200ms ease-out'))
  ]),
]

Update: I have plunker example: https://plnkr.co/edit/iVffRLUhzp43DXo5BYlJ?p=preview (If it failed to load the example, please click on stop and run button several times. It will eventually work).

I want the above animation code to create the silde-out effect when expanding and slide-in effect when collapsing. However the animation only works when expanding. When I try to collapse the menu, it just went disappeared without any animation.

I'm wondering if anyone has ever tried to create a working workaround for collapse for menu vertically for both slide-int and slide-out.

Thanks in advance.

Answer

RobM picture RobM · Feb 23, 2017

You effectively already figured out what is happening, but I dove in and have some additional details on why you were seeing the those results, and an enhancement to the workaround that is a little cleaner and reusable.

Root Cause:

As you noted, ng2-bootstrap's current collapse implementation simply toggle's an elements display style between display: none and display: block, as you can see in the source. This change to the display property nullifies the transition animation (at least currently, browsers don't respect the transition when the display property changes).

It looks like the intended default behavior for the collapse implementation is to have animation, but there's a snag. Since the ng2-bootstrap implementation for collapse uses directives, they are waiting for Angular 2+ support for animations on directives, which doesn't exist -- at least yet (but does on components, as you are currently using). This is a known issue and has been reported here.

Workaround:

You indicated that you intend to have animations in multiple places, which suggests that you could benefit from making the animations reusable -- which would make things more DRY and easier to manage. After messing with a bunch of workaround options for when you need an animation, I think the best is to:

  1. Don't use the ng2-bootstrap's collapse directive for items that are in your template that you need to animate in and out. In your example, the [collapse]=isCollapsed() (as you aleady determined).
  2. Specify animations on the components, as you now are.
  3. Create a class to define your animation(s), making them resuable.
  4. Set the animation from #2 to the appropriate object from #3.

Here is an example:

animations.ts
import { trigger, state, transition, animate, style } from '@angular/core';

export class Animations {
    public static slideInOut = trigger('slideInOut', [
        state('true', style({ height: '0px' })),
        state('false', style({ height: '*' })),
        transition('1 => 0', animate('500ms ease-in')),
        transition('0 => 1', animate('500ms ease-out'))
    ]);
}
app.component.ts
import { Component, trigger, state, style, transition, animate } from '@angular/core';
import { Animations } from './animations';

@Component({
  selector: 'my-app',
  templateUrl: './app/app.component.html',
  styleUrls: ['./app/app.component.css'],
  animations: [ Animations.slideInOut ]
})
export class AppComponent { 
  private collapsed: boolean;

  constructor() {
    this.collapsed = true;
  }

  public isCollapsed(): boolean {
    return this.collapsed;
  }

  public setCollapsed(): void {
    this.collapsed = true;
  }

  public toggleMenu(): void {
    this.collapsed = !this.collapsed;
  }
}
app.component.html
<header>
    <nav class="navbar navbar-fixed-top" role="navigation">
        <div class="container">
            <div class="navbar-header">
                <button type="button" class="navbar-toggle" (click)="toggleMenu()">
          <span class="icon-bar"></span>
          <span class="icon-bar"></span>
          <span class="icon-bar"></span>
        </button>
            </div>
        </div>
    </nav>

    <nav role="navigation" class="navbar-fixed-top-responsive">
        <div class="vertical-menu" [@slideInOut]="isCollapsed()">
            <ul class="menu-item">
                <li><a>menu1</a></li>
                <li><a>menu2</a></li>
                <li><a>menu3</a></li>
            </ul>
        </div>
    </nav>
</header>