This guide was written for Angular 2 version: 2.0.0
Originally, Angular 1.x was created to help developers build enterprise applications faster. With the introduction of ngAnimate
written by Matias Niemelä, Angular 1.x suddenly offered something for everyone. Not only could developers create powerful line of business applications, but designers could use Angular to create rich, immersive experiences. Matias took animations to the next level in Angular 2 by rewriting the entire API to give us complete control over ever facet of how our interfaces are animated.
In this lesson, we are going to examine a CSS animation in Angular 1.x and then translate it to work in Angular 2.
Angular 1.x provides animation functionality through the ngAnimate
module and is entirely class based. The upside to this approach is that it is a non-intrusive process to add animations to an existing Angular application. In most cases, it is as simple as adding a CSS class to your template which we will see in just a moment.
To illustrate animations in Angular 1.x, we are going to build out an application that toggles the visibility of an element when you click a button. Our starting point is an AppComponent
with an empty AppController
and template with a button
and a div
element for which we want to toggle visibility.
class AppController {}
const AppComponent = {
template: `
<div class="container">
<h1>Animations</h1>
<hr>
<button type="button" class="btn btn-primary btn-lg">
Hide
</button>
<div class="alert alert-success">
Animate good times! Come on!
</div>
</div>
`,
controller: AppController
};
angular.module('app', [])
.component('app', AppComponent);
Since we want to toggle the visibility of an element in our template, we will initialize an isVisible
property in the $onInit
lifecycle hook. We will then create a toggleVisibility
method to toggle this.isVisible
between true
and false
.
class AppController {
$onInit() {
this.isVisible = true;
}
toggleVisibility() {
this.isVisible = !this.isVisible;
}
}
We also want to toggle the label of our template button, and so we will add a function to return the appropriate label based on the current value of this.isVisible
.
class AppController {
$onInit() {
this.isVisible = true;
}
getLabel() {
return this.isVisible ? 'Hide' : 'Show';
}
toggleVisibility() {
this.isVisible = !this.isVisible;
}
}
With our controller in place, we will update our template to utilize our new created functionality. We will add ng-click
to our button that calls $ctrl.toggleVisiblity
and bind our button label to whatever value is returned from $ctrl.getLabel
. We will also add an ng-if
to our div
element that will add or remove the element depending on whether or not $ctrl.isVisible
is true
or false
.
<div class="container">
<h1>Animations</h1>
<hr>
<button type="button" class="btn btn-primary btn-lg"
ng-click="$ctrl.toggleVisibility()">
{{ $ctrl.getLabel() }}
</button>
<div ng-if="$ctrl.isVisible" class="alert alert-success">
Animate good times! Come on!
</div>
</div>
At this point, we have an entirely working example minus the animations. You can see the entire code up to this point below.
class AppController {
$onInit() {
this.isVisible = true;
}
getLabel() {
return this.isVisible ? 'Hide' : 'Show';
}
toggleVisibility() {
this.isVisible = !this.isVisible;
}
}
const AppComponent = {
template: `
<div class="container">
<h1>Animations</h1>
<hr>
<button type="button" class="btn btn-primary btn-lg"
ng-click="$ctrl.toggleVisibility()">
{{ $ctrl.getLabel() }}
</button>
<div ng-if="$ctrl.isVisible" class="alert alert-success">
Animate good times! Come on!
</div>
</div>
`,
controller: AppController
};
angular.module('app', [])
.component('app', AppComponent);
With our functionality completed, we will add an animation that will cause our div
to fade in and out instead of just blinking on and off the screen. The point worth emphasizing is just how little we will change the existing code to get this working.
Because ngAnimate
is a separate module from the core framework, we need to add it to our source file and then declare it as a dependency to our main module. We will update our app
module definition to include ngAnimate
in the dependencies array. We have just completed change number one.
angular.module('app', ['ngAnimate'])
.component('app', AppComponent);
Since we want our element to fade in and out, we will add a sufficiently descriptive class to our div
element. With the addition of our fade
class, we have completed change number two.
<div ng-if="$ctrl.isVisible" class="fade alert alert-success">
Animate good times! Come on!
</div>
We still need to define the application, but this happens outside of the existing Angular application. It is generally a good practice to separate out CSS animations into their own CSS file, and so you will commonly see an animations.css
file in a project that uses ngAnimate
.
Within our animations.css
file, we are going to define our fade
class and set it to have 100% opacity.
.fade {
opacity: 1;
}
Animations in Angular 1.x operate on the concept of animation hooks that we can use to define behavior when certain events happen. You can read about all of these hooks in the Angular 1.x documentation, but the two we are going to use for our example are ng-enter
and ng-leave
. We can define custom styles for each lifecycle hook and its current state. To illustrate this, we will set up the animation transition for both hooks to be transition:0.5s linear all
as seen in the code below.
.fade {
opacity: 1;
}
.fade.ng-enter, .fade.ng-leave {
transition:0.5s linear all;
}
When an element enters the DOM, the ng-enter
class establishes the animation starting point and then it transitions to whatever style we define in the ng-enter-active
style. In this case, we are starting with an opacity
of 0
and when ng-enter
has been actively applied aka ng-enter-active
, it will have an opacity
of 1
.
.fade.ng-enter {
opacity:0;
}
.fade.ng-enter.ng-enter-active {
opacity:1;
}
When an element leaves the DOM, the process is the same, but we want to reverse the animation. We will start the leave animation with an opacity
of 1
and will complete the animation with an opacity
of 0
.
.fade.ng-leave {
opacity:1;
}
.fade.ng-leave.ng-leave-active {
opacity:0;
}
You will notice that the enter and leave animations are exactly the same but in reverse. If we desired, we could stack our classes like so to make it a bit more concise.
.fade {
opacity: 1;
}
.fade.ng-enter, .fade.ng-leave {
transition:0.5s linear all;
}
.fade.ng-leave,
.fade.ng-enter.ng-enter-active {
opacity:1;
}
.fade.ng-enter,
.fade.ng-leave.ng-leave-active {
opacity:0;
}
With two small changes to our code and the addition of a few CSS classes, we have gone from something entirely functional to something that not only works well but creates a much better user experience.
Animations in Angular 2 have shifted slightly regarding implementation, but the result is that we can exert significantly more control over every facet of our animations. In Angular 1.x, we had a set of predefined hooks that we could use to trigger our animations whereas, in Angular 2, we can define our own triggers. In Angular 1.x, we also had predefined states that we could define our animations within whereas with Angular 2, we can define as many states as we want and how we want to transition between each state. This freedom essentially opens up an endless spectrum of possibilities for us to use in our applications.
As a starting point, we will begin with an Angular 2 version of the application we used in the sample above. We have an AppComponent
with a simple template that has the same button
and div
element we want to animate in and out.
import { Component } from '@angular/core';
@Component({
selector: 'app',
providers: [],
styles: [],
template: `
<div class="container">
<h1>Animations</h1>
<hr>
<button type="button" class="btn btn-primary btn-lg">
Hide
</button>
<div class="alert alert-success">
Animate good times! Come on!
</div>
</div>
`
})
export class AppComponent { }
We are going to add a visibility
property to our component and initialize it to shown
. We are using a string value instead of boolean true
or false
to so that we can interact with our animation trigger in a moment. We will add a toggleVisibility
method that toggles this.visibility
between hidden
and shown
. While we are at it, we will add our getLabel
method to toggle our button label.
export class AppComponent {
visibility = 'shown';
getLabel() {
return this.visibility == 'shown' ? 'Hide' : 'Show';
}
toggleVisibility() {
this.visibility =
this.visibility == 'shown'
? 'hidden' : 'shown';
}
}
We will update our template to call toggleVisiblity
when the button is clicked and add or remove our element via *ngIf="visibility=='shown'"
.
<div class="container">
<h1>Animations</h1>
<hr>
<button type="button"
class="btn btn-primary btn-lg"
(click)="toggleVisibility()">
{{ getLabel() }}
</button>
<div *ngIf="visibility=='shown'"
class="alert alert-success">
Animate good times! Come on!
</div>
</div>
We have now achieved parity with our Angular 1.x example regarding functionality with the code below.
import { Component } from '@angular/core';
@Component({
selector: 'app',
providers: [],
styles: [
`.alert { margin-top: 10px; }`
],
template: `
<div class="container">
<h1>Animations</h1>
<hr>
<button type="button"
class="btn btn-primary btn-lg"
(click)="toggleVisibility()">
{{ getLabel() }}
</button>
<div *ngIf="visibility=='shown'"
class="alert alert-success">
Animate good times! Come on!
</div>
</div>
`
})
export class AppComponent {
visibility = 'shown';
getLabel() {
return this.visibility == 'shown' ? 'Hide' : 'Show';
}
toggleVisibility() {
this.visibility =
this.visibility == 'shown'
? 'hidden' : 'shown';
}
}
To complete the circle, we need to add an animation to our Angular 2 application. In Angular 2, there are a few more pieces involved than just importing ngAnimate
, but the result is a lot more power. We will update our imports to include trigger
, state
, animate
, transition
and style
.
import { Component, trigger, state, animate, transition, style } from '@angular/core';
We will also add an animations property to our @Component
decorator to hold our animations.
animations: []
With our groundwork completed, the very first thing we need to do is to add an animation trigger. This trigger is what we will use to connect our animations to our template. Because we want to toggle the visibility of an element, we will call trigger
and pass a name of visibility
for our trigger name.
animations: [
trigger('visibility', [])
]
We will then remove the *ngIf
statement from the element below and replace it with [@visibility]="visibility"
.
<div *ngIf="visibility=='shown'"
class="alert alert-success">
Animate good times! Come on!
</div>
We are binding our @visibility
trigger to whatever value visibility
is within our component class. We defined only two possible values for the visibility
property, and we will use them to defined states within our animation.
<div [@visibility]="visibility" class="alert alert-success">
Animate good times! Come on!
</div>
We will define a state for shown
and a state for hidden
and declare custom styles for each state. In the case of our shown
state, we want an opacity
of 1
and an opacity
of 0
if we are in the hidden
state.
animations: [
trigger('visibility', [
state('shown', style({
opacity: 1
})),
state('hidden', style({
opacity: 0
}))
])
]
At this point, our animation will toggle between our two states, but the visual result is exactly the same as if we were using *ngIf
. How do transition from one state to another? We accomplish this by adding a transition
to our visibility
animation with the this line of code transition('* => *', animate('.5s'))
. We are using wildcards to indicate that if we are moving from any state to any other state, we want a half-second animation as the transition.
animations: [
trigger('visibility', [
state('shown', style({
opacity: 1
})),
state('hidden', style({
opacity: 0
})),
transition('* => *', animate('.5s'))
])
]
We now have a working animation within our application and have completed the transition from an Angular 1.x animation to an Angular 2 animation. You can see the entire component code below.
import { Component, trigger, state, animate, transition, style } from '@angular/core';
@Component({
selector: 'app',
providers: [],
styles: [
`.alert { margin-top: 10px; }`
],
animations: [
trigger('visibility', [
state('shown', style({
opacity: 1
})),
state('hidden', style({
opacity: 0
})),
transition('* => *', animate('.5s'))
])
],
template: `
<div class="container">
<h1>Animations</h1>
<hr>
<button type="button"
class="btn btn-primary btn-lg"
(click)="toggleVisibility()">
{{ getLabel() }}
</button>
{{ getLabel() }}
</button>
<div [@visibility]="visibility" class="alert alert-success">
Animate good times! Come on!
</div>
</div>
`
})
export class AppComponent {
visibility = 'shown';
getLabel() {
return this.visibility == 'shown' ? 'Hide' : 'Show';
}
toggleVisibility() {
this.visibility = this.visibility == 'shown' ? 'hidden' : 'shown';
}
}
This lesson provides an introductory example to draw an easy to understand comparison between how animations work in Angular 1.x and Angular 2. We recommend that you check out the Angular 2 documentation to get a full sense of the awesome possibilities that Angular 2 animations provide.