58

I'm playing with the Animation API, and I'd like to create a reusable animation like say sliding in content for top level router views. I managed to get through the funky meta data syntax (which is actually pretty cool once get past the crazy idea of using metadata) to get the animations mostly working.

   @Component({
      //moduleId: module.id,
      selector: 'album-display',
      templateUrl: './albumDisplay.html',
      animations: [
        trigger('slideIn', [
          state('*', style({
            transform: 'translateX(100%)',
          })),
          state('in', style({
            transform: 'translateX(0)',
          })),
          state('out',   style({
            transform: 'translateX(-100%)',
          })),
          transition('* => in', animate('600ms ease-in')),
          transition('in => out', animate('600ms ease-in'))
        ])
      ]
  })
  export class AlbumDisplay implements OnInit {
      slideInState = 'in';
      ...
  }

And then assigning that to my top level element in the component:

  <div  class="container" [@slideIn]="slideInState">

So this works, but how can I make this reusable? I don't want to stick this meta data onto every view. Since this is metadata I'm not sure how you could make this reusable.

Rick Strahl
  • 17,302
  • 14
  • 89
  • 134
  • Don't forget to type the trigger: export const myTrigger:AnimationEntryMetadata = trigger( ...... or you will get a build error. – user2734839 Dec 25 '16 at 00:26

5 Answers5

103

One possible way is to put animation trigger code in separate file and export it as const variable and use it in component as below.

animations.ts

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

export const slideIn = trigger('slideIn', [
  state('*', style({
    transform: 'translateX(100%)',
  })),
  state('in', style({
    transform: 'translateX(0)',
  })),
  state('out',   style({
    transform: 'translateX(-100%)',
  })),
  transition('* => in', animate('600ms ease-in')),
  transition('in => out', animate('600ms ease-in'))
]);

album-display.component.ts

import { slideIn } from './animations'; // path to your animations.ts file

@Component({
    //moduleId: module.id,
    selector: 'album-display',
    templateUrl: './albumDisplay.html',
    animations: [
      slideIn
    ]
})
export class AlbumDisplay implements OnInit {
    slideInState = 'in';
    ...
}
Hiren Gohel
  • 4,942
  • 6
  • 29
  • 48
ranakrunal9
  • 13,320
  • 3
  • 42
  • 43
  • 2
    Thank you that worked perfectly. After initially thinking WTF about this funky meta based implementation, after creating a few using this syntax, it turns out its very easy to create animations for components. – Rick Strahl Sep 17 '16 at 16:40
  • 3
    This solution will prevent you from using AOT-Compiling :/ – Braincompiler Mar 03 '17 at 14:08
  • 1
    is there a way to use this **without** applying `[@slideIn]` manually to every single element? Like in ng1, where all elements would animate nicely automatically – phil294 Apr 27 '17 at 15:13
  • 2
    I know this an old topic, but any ideas about how to use it with AOT compilation? – Will Nov 06 '17 at 22:46
  • 4
    this does work with AOT - perhaps it didn't a year ago but it does now – Simon_Weaver Sep 27 '18 at 06:07
28

Maybe it's a bit late, but I'd still like to put an answer for a more dynamic version of the trigger.

Put animation trigger code in separate file and export it as function.

translate.ts

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

export function TranslateX( name: string, x: string, duration: number ): AnimationEntryMetadata {
    return trigger( name, [
            state('false', style({ transform: 'translateX(0)' }) ),
            state('true',  style({ transform: 'translateX(' + x + ')' }) ),
            transition('0 => 1', animate( duration + 'ms ease-in')),
            transition('1 => 0', animate( duration + 'ms ease-out')),
        ]);
}

so, in the Component app.component.ts

import { TranslateX } from './translate';

@Component({
    ...
    templateUrl: './app.component.html',
    animations:   [ 
                    TranslateX( 'trigger1Title','-100%', 200 ),
                    TranslateX( 'trigger2Title','20vw', 700 )
                  ]
    ...
})

and in template app.component.html

...
<div [@trigger1Title]="token1"> ... </div>
<div [@trigger2Title]="token2"> ... </div>
...

You can customize the trigger with more input data, for example separating the transition times, and so on.

Alexander Paul
  • 423
  • 1
  • 6
  • 11
  • 5
    This will fail when used with AOT compilation – Trash Can Nov 24 '17 at 06:22
  • Your statement is correct only if to build trigger are used classes, enums, or internal calls are made to other functions. I'm currently working on Angular 4, TypeScript 2.4.2 and Written as above, everything works as I expect. – Alexander Paul Nov 26 '17 at 15:21
  • Looks like it will fail if you use template string (backticks) instead of the traditional string concatenation using the plus sign `+` – Trash Can Dec 01 '17 at 06:55
  • 2
    You're right, and, as you can see, in the example no backticks are used. – Alexander Paul Dec 06 '17 at 10:10
  • This doesn't seem to work in Angular 6. For starters `AnimationEntryMetadata` doesn't exist anymore, but I get 'Unable to resolve animation metadata node #undefined' when trying to use this method with a very simple animation. – Simon_Weaver Sep 27 '18 at 06:10
  • 1
    @Simon_Weaver It has been replaced by `AnimationTriggerMetadata` – John Dec 06 '18 at 14:32
2

Router Animation Example in Angular 4

I just finished tackling router animations myself using Angular 4, here's a couple of example animations I came up with for a fade in transition and slide in/out transition.

Check out this post for more details and a live demo.

Angular 4 Slide in/out animation

// import the required animation functions from the angular animations module
import { trigger, state, animate, transition, style } from '@angular/animations';
 
export const slideInOutAnimation =
    // trigger name for attaching this animation to an element using the [@triggerName] syntax
    trigger('slideInOutAnimation', [
 
        // end state styles for route container (host)
        state('*', style({
            // the view covers the whole screen with a semi tranparent background
            position: 'fixed',
            top: 0,
            left: 0,
            right: 0,
            bottom: 0,
            backgroundColor: 'rgba(0, 0, 0, 0.8)'
        })),
 
        // route 'enter' transition
        transition(':enter', [
 
            // styles at start of transition
            style({
                // start with the content positioned off the right of the screen,
                // -400% is required instead of -100% because the negative position adds to the width of the element
                right: '-400%',
 
                // start with background opacity set to 0 (invisible)
                backgroundColor: 'rgba(0, 0, 0, 0)'
            }),
 
            // animation and styles at end of transition
            animate('.5s ease-in-out', style({
                // transition the right position to 0 which slides the content into view
                right: 0,
 
                // transition the background opacity to 0.8 to fade it in
                backgroundColor: 'rgba(0, 0, 0, 0.8)'
            }))
        ]),
 
        // route 'leave' transition
        transition(':leave', [
            // animation and styles at end of transition
            animate('.5s ease-in-out', style({
                // transition the right position to -400% which slides the content out of view
                right: '-400%',
 
                // transition the background opacity to 0 to fade it out
                backgroundColor: 'rgba(0, 0, 0, 0)'
            }))
        ])
    ]);

Angular 4 Fade in animation

// import the required animation functions from the angular animations module
import { trigger, state, animate, transition, style } from '@angular/animations';
 
export const fadeInAnimation =
    // trigger name for attaching this animation to an element using the [@triggerName] syntax
    trigger('fadeInAnimation', [
 
        // route 'enter' transition
        transition(':enter', [
 
            // css styles at start of transition
            style({ opacity: 0 }),
 
            // animation and styles at end of transition
            animate('.3s', style({ opacity: 1 }))
        ]),
    ]);

Component with transition attached

import { Component } from '@angular/core';
 
// import fade in animation
import { fadeInAnimation } from '../_animations/index';
 
@Component({
    moduleId: module.id.toString(),
    templateUrl: 'home.component.html',
 
    // make fade in animation available to this component
    animations: [fadeInAnimation],
 
    // attach the fade in animation to the host (root) element of this component
    host: { '[@fadeInAnimation]': '' }
})
 
export class HomeComponent {
}
Jason Watmore
  • 4,521
  • 2
  • 32
  • 36
  • Good examples. Is there a reason you used absolute for moving instead of translate which is far better optimized? – J J B Apr 22 '17 at 22:43
  • @JJB Yes I used a negative right position to move the form while leaving the background in place to fade out, when I tried with translate it slides the form and background off the page. I couldn't find a way to animate child elements on the ':leave' transition, otherwise I would have used a separate div for the background and form where I could have used translate. This is the only way I could figure out to fade in the background and slide in the form using only a single html element. – Jason Watmore Apr 26 '17 at 08:05
1

Proper solution would be to have animations supported in Directives.

It is not the case, but there is a issue for that on Angular's Github: https://github.com/angular/angular/issues/9947

Hope it will be solved soon.

rcomblen
  • 4,579
  • 1
  • 27
  • 32
1

With a class and you can extended,

import { trigger, style, state, animate, transition, AnimationMetadata } from "@angular/core";
export class MyAwesomeAnimations {

    /**
     * 
     * @param nameTrigger Name of triggers
     * @param setNewsStates add states for animations
     * @param setNewTransitions add transitions for states
     */
    SetTrigger(nameTrigger: string, setNewsStates?: AnimationMetadata[], setNewTransitions?: AnimationMetadata[]): any {
        let metaData: AnimationMetadata[] = [
            state('*', style({
                transform: 'translateX(100%)',
            })),
            state('in', style({
                transform: 'translateX(0)',
            })),
            state('out', style({
                transform: 'translateX(-100%)',
            })),
            transition('* => in', animate('600ms ease-in')),
            transition('in => out', animate('600ms ease-in'))
        ];
        if (setNewsStates)
            metaData.concat(setNewsStates);
        if (setNewTransitions)
            metaData.concat(setNewTransitions);


        return trigger(nameTrigger, metaData);
    }
}

For use

    @Component({
        selector: "orden-detail-component",
        animations: [new MyAwesomeAnimations().SetTrigger("slideIn")],
        template:"<div  class="container" [@slideIn]="slideInState">"
    })
    export class OrdenDetailComponent {
       slideInState = 'in';
    }

I hope that way can help you