April 19 2017

Angular 2/4 - Router Animation Tutorial & Example

In this tutorial I'll show you how you can implement animations between routes in Angular 2 / Angular 4 and TypeScript.

Project code is available on GitHub at https://github.com/cornflourblue/angular2-animation-tutorial-example.

To see the same example in AngularJS / Angular 1 you can check out AngularJS ngAnimate Tutorial & Example with UI Router.


Difference Between Angular 2/4 Animations and AngularJS Animations

Animations in Angular 2/4 work completely differently to Angular 1, in Angular 1 there are css class hooks that you can use to animate elements into view and out of view, whereas in Angular 2/4 animations are implemented inside your components using a set of functions (trigger, state, animate, transition, style) from the new '@angular/animations' package.

Animation styles are still defined using CSS but they're written in TypeScript using JSON objects (TSON?) instead of CSS/LESS files, there's also a new Angular Animation DSL (Domain Specific Language) that has been created to define different states and transitions between states. I won't go too much into the details of all the new bits and pieces of the animation system since you can find all that information on the angular docs site, instead I'll focus on an example and how to get animations working between routes.

Don't feel bad if the new angular animation system has had you banging your head against the wall in frustration, it's taken me an embarrassingly long time to get my head around and I'm still coming to grips with it!


Angular 2/4 Animations Tutorial Demo

Here's the tutorial example application in action, it uses a fade transition between top level tabs and a slide in/out transition on the products page for adding and editing a product.

(See on Plunker at http://plnkr.co/edit/AfIB1i?p=preview)


Running the Angular 2/4 Animation Tutorial Demo Locally

  1. Install NodeJS and NPM from https://nodejs.org/en/download/, you can check the versions you have installed by running node -v and npm -v from the command line.
     
  2. Download the project source code from https://github.com/cornflourblue/angular2-animation-tutorial-example
     
  3. Install all required npm packages by running npm install from the command line in the project root folder (where the package.json is located).
     
  4. Start the application by running npm start from the command line in the project root folder.


Let's stay focused on the animations

I tried to make the example a real world-ish application so it contains a few services and other bits and pieces but I'm just going to focus on the animations in this post and how to add them to your Angular 2/4 routes.


Import BrowserAnimationsModule into app.module.ts

With the release of Angular 4, animations were split out from the angular core into their own module, so to use animations you have to import the BrowserAnimationsModule at the top of your app.module.ts file:

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


And add the BrowserAnimationsModule to the list of imports in your @NgModule metadata:

@NgModule({
    imports: [
        ...
        BrowserAnimationsModule
    ],
    ...
})
export class AppModule { }


The first import makes BrowserAnimationsModule available to code inside the app.module.ts file, and the second import makes all the public functions from BrowserAnimationsModule available to other components in AppModule.


Define Your Angular 2/4 Animations

Animations can be defined directly inside your components but I prefer to separate them into their own files, this allows you to re-use the animations anywhere in your application and makes component code much cleaner as it maintains a good separation of concerns.

In the example I've put animations in the app/_animations folder, I like to prefix non-feature folders with an underscore '_' to group them together at the top and separate them from feature folders. Feature folders contain code for views / routes in your application such as home and products, non-feature folders contain shared code such as services, animations, directives, css etc, basically everything else.


Angular 2/4 Fade In Animation

The fade in animation that's used by the home component and product list component in the example.

// 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 }))
        ]),
    ]);


Angular 2/4 Slide In/Out Animation

The slide in/out animation that's used by the product add/edit component in the example.

// 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)'
            }))
        ])
    ]);


Attach the Angular Animations to Your Routed Components

With the animations cleanly separated into their own files it's easy to attach them to your angular routes, all you need to do is import the animation you want to use and add it to your @Component metadata.


Angular 2/4 Home Component with Fade In Animation

The home component from the example with the fade in animation 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 {
}


Angular 2/4 Product List Component with Fade In Animation

The product list component from the example with the fade in animation attached.

import { Component, OnInit, OnDestroy } from '@angular/core';
import { Subscription } from 'rxjs/Subscription';

import { ProductService, PubSubService } from '../_services/index';

// import fade in animation
import { fadeInAnimation } from '../_animations/index';

@Component({
    moduleId: module.id.toString(),
    templateUrl: 'product-list.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 ProductListComponent implements OnInit, OnDestroy {
    products: any[];
    subscription: Subscription;

    constructor(
        private productService: ProductService,
        private pubSubService: PubSubService) { }
    
    deleteProduct(id: number) {
        this.productService.delete(id);
        this.loadProducts();
    }

    ngOnInit() {
        this.loadProducts();

        // reload products when updated
        this.subscription = this.pubSubService.on('products-updated').subscribe(() => this.loadProducts());
    }

    ngOnDestroy() {
        // unsubscribe to ensure no memory leaks
        this.subscription.unsubscribe();
    }

    private loadProducts() {
        this.products = this.productService.getAll();
    }
}


Angular 2/4 Product Add/Edit Component with Slide In/Out Animation

The product add/edit component from the example with the slide in/out animation attached.

import { Component, OnInit } from '@angular/core';
import { Router, ActivatedRoute } from '@angular/router';

import { ProductService, PubSubService } from '../_services/index';

// import slide in/out animation
import { slideInOutAnimation } from '../_animations/index';

@Component({
    moduleId: module.id.toString(),
    templateUrl: 'product-add-edit.component.html',

    // make slide in/out animation available to this component
    animations: [slideInOutAnimation],

    // attach the slide in/out animation to the host (root) element of this component
    host: { '[@slideInOutAnimation]': '' }
})

export class ProductAddEditComponent implements OnInit {
    title = 'Add Product';
    product: any = {};

    constructor(
        private route: ActivatedRoute,
        private router: Router,
        private productService: ProductService,
        private pubSubService: PubSubService) { }

    ngOnInit() {
        let productId = Number(this.route.snapshot.params['id']);
        if (productId) {
            this.title = 'Edit Product';
            this.product = this.productService.getById(productId);
        }
    }

    saveProduct() {
        // save product
        this.productService.save(this.product);

        // redirect to users view
        this.router.navigate(['products']);

        // publish event so list controller can refresh
        this.pubSubService.publish('products-updated');
    }
}


Recommended Books on Angular 2/4


Web Developer Sydney

Feel free to drop me a line if you're looking for an Angular 2/4 developer or web developer in Sydney Australia, I also provide remote contracting services for clients outside Sydney.


Sponsored by