71

What is the bare minimum and Angular4's native way to slide in and slide out a container element?

e.g.

<div ngIf="show">
    <!-- Content -->
</div>

Slide In Content (from top to down just like jQuery.slideDown()) when show turns to true.

Slide Out Content (suitably with ease-out effect) when show turns to false.

Hoff
  • 38,776
  • 17
  • 74
  • 99
abdul-wahab
  • 2,182
  • 3
  • 21
  • 35

4 Answers4

183

First some code, then the explanation. The official docs describing this are here.

import { trigger, transition, animate, style } from '@angular/animations'

@Component({
  ...
  animations: [
    trigger('slideInOut', [
      transition(':enter', [
        style({transform: 'translateY(-100%)'}),
        animate('200ms ease-in', style({transform: 'translateY(0%)'}))
      ]),
      transition(':leave', [
        animate('200ms ease-in', style({transform: 'translateY(-100%)'}))
      ])
    ])
  ]
})

In your template:

<div *ngIf="visible" [@slideInOut]>This element will slide up and down when the value of 'visible' changes from true to false and vice versa.</div>

I found the angular way a bit tricky to grasp, but once you understand it, it quite easy and powerful.

The animations part in human language:

  • We're naming this animation 'slideInOut'.

  • When the element is added (:enter), we do the following:

  • ->Immediately move the element 100% up (from itself), to appear off screen.

  • ->then animate the translateY value until we are at 0%, where the element would naturally be.

  • When the element is removed, animate the translateY value (currently 0), to -100% (off screen).

The easing function we're using is ease-in, in 200 milliseconds, you can change that to your liking.

starball
  • 20,030
  • 7
  • 43
  • 238
Hoff
  • 38,776
  • 17
  • 74
  • 99
  • 3
    This *works*, and that's what I care about the most :D – pulsejet Mar 11 '18 at 06:09
  • 5
    With Angular 5 I also had to add BrowserAnimationsModule to the imports list in app.module.ts. – slasky Mar 23 '18 at 21:10
  • 1
    I'm having a hard time understanding why angular's samples like to define a default `'in'` state like `state('in', style({transform: 'translateX(0)'})),` when as you've shown it's really not necessary with an *ngIf. – Simon_Weaver Jun 11 '18 at 00:39
  • 1
    I upvoted this answer a long time ago...finally was able to use it!! lord jesus! you are great – Patricio Vargas Sep 24 '18 at 20:25
  • 2
    @Simon_Weaver I think they are doing this to define "states" of design, that you can transition to for example. So "In" is something you could tell the animation you are doing. or for a more clear example "hasNoData" vs "hasData" or "isLoadedFromPage" vs "isLoadedFromModal". With those states defined you can then define transitions between them. "in" and "out" is easy to understand. "hasData" to "hasNoData" maybe you fade out something/fade in something else. etc :) – Jessy Jan 30 '19 at 20:13
  • 2
    Any reason this wouldn't work? The div just disappears. No errors. – xtrinch Feb 22 '19 at 16:17
  • Seems to work ok with height rather than transform also - and I used height:'*' for :enter - transform is quite an ugly transition – Rob Jul 01 '20 at 00:28
42

I answered a very similar question, and here is a way of doing this :

First, create a file where you would define your animations and export them. Just to make it more clear in your app.component.ts

In the following example, I used a max-height of the div that goes from 0px (when it's hidden), to 500px, but you would change that according to what you need.

This animation uses states (in and out), that will be toggle when we click on the button, which will run the animtion.

animations.ts

import { trigger, state, style, transition,
    animate, group, query, stagger, keyframes
} from '@angular/animations';

export const SlideInOutAnimation = [
    trigger('slideInOut', [
        state('in', style({
            'max-height': '500px', 'opacity': '1', 'visibility': 'visible'
        })),
        state('out', style({
            'max-height': '0px', 'opacity': '0', 'visibility': 'hidden'
        })),
        transition('in => out', [group([
            animate('400ms ease-in-out', style({
                'opacity': '0'
            })),
            animate('600ms ease-in-out', style({
                'max-height': '0px'
            })),
            animate('700ms ease-in-out', style({
                'visibility': 'hidden'
            }))
        ]
        )]),
        transition('out => in', [group([
            animate('1ms ease-in-out', style({
                'visibility': 'visible'
            })),
            animate('600ms ease-in-out', style({
                'max-height': '500px'
            })),
            animate('800ms ease-in-out', style({
                'opacity': '1'
            }))
        ]
        )])
    ]),
]

Then in your app.component, we import the animation and create the method that will toggle the animation state.

app.component.ts

import { SlideInOutAnimation } from './animations';

@Component({
  ...
  animations: [SlideInOutAnimation]
})
export class AppComponent  {
  animationState = 'in';

  ...

  toggleShowDiv(divName: string) {
    if (divName === 'divA') {
      console.log(this.animationState);
      this.animationState = this.animationState === 'out' ? 'in' : 'out';
      console.log(this.animationState);
    }
  }
}

And here is how your app.component.html would look like :

<div class="wrapper">
  <button (click)="toggleShowDiv('divA')">TOGGLE DIV</button>
  <div [@slideInOut]="animationState" style="height: 100px; background-color: red;">
  THIS DIV IS ANIMATED</div>
  <div class="content">THIS IS CONTENT DIV</div>
</div>

slideInOut refers to the animation trigger defined in animations.ts

Here is a StackBlitz example I have created : https://angular-muvaqu.stackblitz.io/

Side note : If an error ever occurs and asks you to add BrowserAnimationsModule, just import it in your app.module.ts:

import { BrowserAnimationsModule } from '@angular/platform-browser/animations';

@NgModule({
  imports: [ ..., BrowserAnimationsModule ],
  ...
})
br.julien
  • 3,420
  • 2
  • 23
  • 44
23

Actually the minimum amount of Angular to be used (as requested in the original question) is just adding a class to the DOM element when show variable is true, and perform the animation/transition via CSS.

So your minimum Angular code is this:

<div class="box-opener" (click)="show = !show">
    Open/close the box
</div>

<div class="box" [class.opened]="show">
    <!-- Content -->
</div>

With this solution, you need to create CSS rules for the transition, something like this:

.box {
    background-color: #FFCC55;
    max-height: 0px;
    overflow-y: hidden;
    transition: ease-in-out 400ms max-height;
}

.box.opened {
    max-height: 500px;
    transition: ease-in-out 600ms max-height;
}

If you have retro-browser-compatibility issues, just remember to add the vendor prefixes in the transitions.

See the example here

Ferie
  • 1,358
  • 21
  • 36
  • 1
    Simple, clean and worked pretty well for me. Thanks! Note: Works for elements with 'block' display and not td, tr. – jtk Oct 11 '20 at 03:55
  • however the question also gave the starting point of ngIf... with ngIf the HTML elements are not in the DOM. with the CSS solution here the elements are always in the DOM. – Adam Marshall Aug 23 '22 at 09:11
  • He did not ask for a solution using `ngif`, he clearly asks for a "bare minimum and Angular4's native way": this one is still valid even today with the latest Angular version – Ferie Sep 07 '22 at 21:27
  • 1
    I see this a lot and it's such a simple and clean solution. – Ole Jan 17 '23 at 17:36
9

The most upvoted answer is not implementing a real slide in/out (or down/up), as:

  1. It's not doing a soft transition on the height attribute. At time zero the element already has the 100% of its height producing a sudden glitch on the elements below it.
  2. When sliding out/up, the element does a translateY(-100%) and then suddenly disappears, causing another glitch on the elements below it.

You can implement a slide in and slide out like so:

my-component.ts

import { animate, style, transition, trigger } from '@angular/animations';

@Component({
  ...
  animations: [
    trigger('slideDownUp', [
      transition(':enter', [style({ height: 0 }), animate(500)]),
      transition(':leave', [animate(500, style({ height: 0 }))]),
    ]),
  ],
})

my-component.html

<div @slideDownUp *ngIf="isShowing" class="box">
  I am the content of the div!
</div>

my-component.scss

.box {
  overflow: hidden;
}
andrescmasmas
  • 171
  • 3
  • 2