Animations in Angular 4.x
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!