Animations in Angular 4.x

Matias Niemelä (@yearofmoo)

http:// yom.nu / ng-conf-2017
www.yearofmoo.com

NG Animations

Matias

yearofmoo.com

Links / Slides

Slides = yom.nu/ng-conf-2017

Demo = yom.nu/ng-conf-2017-demo

Github = yom.nu/ng-conf-2017-code

What to expect

Lots of new features to show

Everything here is for 4.1

A full demo website is here

Setup

@angular/animations

We now have @angular/animations

Animations are now separate from core

Totally refactored / fully dynamic

// in your component code import { style, animate, transition, trigger } from "@angular/animations";
// in your module code import {BrowserAnimationsModule} from "@angular/platform-browser/animations"; @NgModule({ imports: [BrowserAnimationsModule] }) //...

Animation Modules

BrowserAnimationsModule for users

NoopAnimationsModule for testing

SystemJS

Include the following:

animations.umd

animations-browser.umd

platform-browser-animations.umd

basic example

How do you fade in/out something?

// demo-fader.component.html <div [@fade]="active ? 'in' : 'out'"> hello there </div> <button (click)="toggleActive()"> Toggle </button>
// demo-fader.component.ts import {trigger, ...} from '@angular/animatons'; @Component({ animations: [ trigger('fade', [ /*...*/ ]) ] })
// demo-fader.component.ts import {fadeAnimations} from './demo-fade-animations'; @Component({ animations: [fadeAnimations] })
// demo-fade-animations.ts export fadeAnimations = trigger('fade', [ // when we are fading in transition('* => in', [ style({ opacity: 0 }), animate(1000, style({ opacity: 1 })) ]) //... ])
trigger('fade', [ //... // when we are fading in transition('* => out', [ animate(1000, style({ opacity: 0 })) ]) ])
// demo-fader.component.html <div [@fade]="active ? 'in' : 'out'" (@fade.start)="onStart($event)" (@fade.done)="onDone($event)"> hello there </div>
$event == { element: any, // div element fromState: string, // "in" or "out" toState: string, // "in" or "out" totalTime: number, // 1000 (milliseconds) triggerName: string, // "fade" phaseName: string // "start" or "done" }

building animation()s

For making animations reusable

the animation() function

defines a reusable animation

input variables

transition() uses it

sub animation()s

animateChild() is used to invoke it

@fade animation refactor

Fade/in are common

Better to have a common recipe

// demo-fade-animations.ts export var fadeIn = animation([ style({ opacity: 0 }), animate(1000, style({ opacity: 1 })) ]); export var fadeOut = animation([ // the starting opacity is auto detected animate(1000, style({ opacity: 0 })) ]);
// demo-fader.component.ts import {fadeIn, fadeOut} from './fade-animation'; trigger('fade', [ transition('* => in', animateChild(fadeIn)) transition('* => out', animateChild(fadeOut)) ])

input variables

animateChild() accepts input params

animateChild(animation, { input: 'param' })

// fade-animation.ts export var fadeIn = animation([ style({ opacity: '$start' }), animate(1000, style({ opacity: '$end' })) ], { end: 1 });
export var fadeOut = animation([ // the starting opacity is auto detected animate('$duration', style({ opacity: 0 })) ], { duration: 1000 });
import {fadeIn, fadeOut} from './fade-animation'; trigger('fade', [ transition('* => in', animateChild(fadeIn, { start: 0 })) transition('* => out', animateChild(fadeOut, { duration: '$customTime' })) ], { customTime: '2s' })

Binding data from the Component

<div [@fade]="exp"></div> @Component({ ... }) class DemoFadeComponent { exp = 'in'; }

Binding data from the Component

<div [@fade]="exp"></div> @Component({ ... }) class DemoFadeComponent { exp = { value: 'in', customTime: 1000 }; }

query() animations

query() and queryAll()

Collects inner items

Uses querySelector/querySelectorAll

Can detect enter/leave & active animations

<div class="container" *ngIf="active" @reveal> <h2>title</h2> <p>text</p> <p>text</p> <img /> </div>
trigger('reveal', [ transition(':enter', [ // 1. shrink the container to be small // 2. hide all inner children // 3. animate the container and inner children ]) ]),
// 1. style({ overflow: 'hidden', height: 0 }), // container // 2. queryAll('*', style({ opacity: 0 })) // children // 3. group([ animate('0.5s', style({ height: '*' })), // container queryAll('*', animate(500, style({ opacity: 1 })) // children ])

programmatic animations

AnimationBuilder

Builds animatons on the fly

Player access

frame-by-frame control

class DemoLoadingComponent { constructor(builder: AnimationBuilder) { this._builder = builder; } start(percentage) { //... } }
start(percentage: number) { const definition = this._builder.build([ style({ width:0 }), anmate(1000, style({ width: (percentage * 100) + '%' })) ]); const player = definition.create(element); return player; }
player.play(); player.pause(); player.finish(); player.onDone(callback); player.setPosition(percentage); player.destroy();

route animations

Animations + Routes

RouteOutlet contains the data

Transitions = route changes

<div [@routerAnimations]="prepareRoute(r)"> <router-outlet #r="outlet"></router-outlet> </div>
class AppCmp { prepareRoute(r) { return r.activeRoute ? r.activeRoute.config.animation : ''; } }
const ROUTES = [{ path: '', component: IndexPageComponent, animation: { value: 'home', // more data }, { path: '', component: AboutPageComponent, animation: { value: 'about', // more data }];
trigger('routerAnimations', [ transition('home => about', [ query('home-page', [...]), query('about-page', [...]) ]), transition('* => *', [ query(':enter', [...]), query(':leave', [...]) ]) ])

Final Notes

Remember: Angular 4.1

Slides = yom.nu/ng-conf-2017

Demo = yom.nu/ng-conf-2017-demo

Github = yom.nu/ng-conf-2017-code

Thank you guys!