Angular & Animations: bring life to your apps

Motion helps to understand with beauty

I’ve always admired animations, it gives soul to the app, converting it in a living entity that reacts to our actions. This helps to understand its functionality and make us empathize with it.

Animation consists in transition an element from an initial state to a final state, calculating the middle states to provide the appearance of motion.

CSS Animations

The basic property that allows to animate an element with CSS is transition:

.element {
 transition: [property] [duration] [ease] [delay]
}

Within it we can declare the [property] we want to animate, the [duration] of the animation, the type of flow [ease], and the [delay].

We can animate only:
- Elements that have a defined initial state to transition from (i.e: width: 200px) and a final state to transition to (i.e: width: 400px).
- Properties that the browser can calculate its middles states, like width, height, font-size…
We can trigger a CSS transition with:
- CSS: declaring a :hover pseudo-class.
- JS: adding a class: 
document.getElementsByClassName(‘circle’)[0].classList.add(“square”);

>> Editor: Hover the yellow circle to see the transition in action and check the app.component.scss to see the implementation:

Performance:
For performance reasons, always limit your animations to “transform” (translate, scale, rotate) and “opacity” properties.

Ok, now that you are an CSS animation expert, let’s see how to apply it to Angular.

Angular Animations

Angular Animations are based in the Web Animations API that basically brings all the power of the animations from the CSS to JS, allowing to do awesome things like pausing or reversing animations right in the code:

let elem = document.getElementsByClassName('circle')[0];
elem.animate({ transform: 'scale(0)', opacity: 0 }, 3000);
elem.pause();

Setting up animations

To start using animations in our Angular project we’ll need:

  • Project:
    Install BrowserModule and BrowserAnimationsModule and add them to the package.json (npm install --save @angular/animations @angular/platform-browser) .
  • Module:
    Add them to the modules you we’ll be applying animations (usually the app.module.ts or shared.module.ts).
  • Component:
    - Import the functions you’ll use { trigger, state, style, animate, transition } from '@angular/animations'
    - Declare the animation in the component’s decorator.
import { Component, OnInit } from '@angular/core';
import { trigger, state, style, animate, transition, group } from '@angular/animations';
@Component({
selector: 'my-app',
templateUrl: './app.component.html',
styleUrls: ['./app.component.scss'],
animations: [ ...animation declarations ]
})
export class AppComponent...

Using animations

// app.component.ts
@Component({
...
animations: [
trigger('triggerName', [
transition('initialState => finalState'
, [
animate('1500ms ease-in')
])
])
],
...
})

An animation declaration is composed at least by a:

  • trigger([name], [definitions array]):
    Declares the animation with a name, used to apply it in the template.
  • transition([states], [definitions array]):
    Specifies the two states (initialState and finalState) that integrate the animation. ‘=>’ operator is used for unidirectional transitions, and ‘<=>’ for bidirectional ones.
  • animation(‘[duration] [delay] [ease]’):
    Defines the kind of animation that will be applied to the transition between the states.

After declaring the animation in the component, we can apply it to the element we want to animate in the template.

// app.component.html
<div @enterLeave>
<p>I'm going to be animated guys...</p>
</div>

Note that we are no using CSS anymore, all the animation is managed in JS and HTML.

States

Like in any animation, Angular animations are just transitions between two styled states. There are 2 types of states:

  • Defaults:
    - void : when the element is not present in the view, it is in the void state.
    - * : wildcard, match any state.
  • Custom:
    States defined by us using state().

Common transitions with default states are “void => *” (when the element enters) and “* => void” (when the element leaves).

trigger('enterLeave', [
// Animate 1500ms with ease-in flow any element that
// transitions from void state (not present) to any other state

transition('void => *', [ // This transition's alias is ':enter'
animate('1500ms ease-in')
])
])

If we apply this animation to an element… we’ll see that nothing happens. That is because in the ‘void’ state the element is not present in the view, so it doesn’t have any style to transition from.

Without an styled Initial State, the browser can’t calculate the transition of those properties to the Final State.

By default, the Final State is the element naturally placed in the view, with the properties we have applied to it in CSS.

Styles

We can define styles with the style() function.

There are two kinds of styles:

  • State styles:
    Applied to the element while it is in the state. Removed when the state changes.
  • Transition styles:
    Applied to the element while it is transitioning to its Final State. Removed when the Final State is applied (with its State Styles) .

Inside Transition Styles, there are also two types:

  • ‘From’ styles:
    Placed at the beginning of the transition, they will be applied to the element right when it’s created.
trigger('enterLeave', [
transition('void => *', [
// 'From' Style
style({ opacity: 0.2, transform: 'translateX(-100%)' }),
animate('1500ms ease-in')
])
])

This way, the element will have an style when the Initial State is ‘void’ and the browser will be able to transition from it. Solved, the transition now works and we can see a quite transparent element (opacity: 0.2) that enters from the left side of the screen (translateX(-100%)) and travels to its final state in the center of the page increasing its opacity.

  • ‘To’ styles:
    Placed inside animate(), they will be used to transition to it from the ‘from’ style, in this case ‘void’.
trigger('enterLeave', [
transition('void => *', [
// 'From' Style
style({ opacity: 0.2, transform: 'translateX(-100%)' }),
animate('1500ms ease-in',
// 'To' Style
style({ opacity: 1, transform: 'scale(1.5)' }),
)
])
])

Now we can see the element entering from the left side of the screen, but this time it scales its size by a 50% before going back abruptly to its finale state, in the center of the page.

‘To’ and ‘From’ styles are removed when the transition completes, that’s why the transition ends abruptly showing the element with its original CSS styles.

>> Editor: Check the app.component.ts in the editor below. Click Hide/Show button to apply/remove the ‘void’ state to the elements.

Let’s remove this ‘To’ styles and add a cool ‘leave’ animation:

trigger('enterLeave', [
transition('void => *', [
// 'From' Style
style({ opacity: 0.2, transform: 'translateX(-100%)' }),
animate('1500ms ease-in')
]),
transition('* => void', [
animate('1500ms ease-in',
// 'To' Style
style({ opacity: 1, transform: 'translateX(100%)' }),
)
])

])

>> Editor: 
1- Comment the ‘To’ styles to remove the growing of the item.
2- Uncomment the ‘
:LEAVE TRANSITION’ in app.component.ts to see the cool.

Cool, now the element appears from the left when it is created (void) thanks to its ‘From’ style described in the transition.

Then, when its removed, it transitates to ‘void’ again, leaving the screen by its right thanks to the ‘To’ styles.

When the transition ends, all the transition styles are removed and the elements goes back to its Final State style (void === not present). Because the element is outside of the window, we don’t see an abrupt disappearance.

Managing States

Besides Default States, we can create our own Custom States and manage when are applied to the element:

trigger('selected', [
// Custom state
state('selected',
style({
backgroundColor: 'whitesmoke',
transform: 'scale(1.2)',
})
),

// When the element goes from 'selected' state to whatever...
transition('selected <=> *', [
animate('300ms ease-in')
])
])

In the view, our animation trigger receives the elementState variable from the component class.

// app.component.html
<div [@selected]="elementState">
<p>I'm going to be animated guys...</p>
</div>

When elementState === ‘selected’, the styles of the ‘selected’ state will be applied to it. When the state changes, the element will transition to any other state as defined (animate(‘300ms ease-in’)).

We can manage the element’s state changing the elementState value with a method like in the sample.

>> Editor: Click on the select blue button of the items to apply the ‘selected’ state and fire the animation.

Also you can group() animations to run them in parallel.

transition('selected => *', [
group([
// Apply pink color to the item and
animate('1s ease',
style({
backgroundColor: '#ffc107'
})
),
// after a second, fade it to the background
animate('2s 1.5s ease',
style({
opacity: 0.2,
transform: 'scale(0.5)'
})
)
])
])

>> Editor: 
3-
Comment the ‘selected ’ state ‘transition’ and uncomment GROUPED ANIMATIONS to see them in action.

One last trick, do you need to fire any action when the animation finishes? Easy, jus bind to its ‘done’ event.

<div class="car" [@selected]="car.selected (@selected.done)="logIt($event)">

That’s it! I didn’t thought it would be this looooong 😮 . I hope you enjoyed it, thanks for reading.

You’ve learned something new, you deserve a gift: