In Angular 1.x we can use ngAnimate to detect when we are leaving or entering a particular route. Furthermore we are able to apply behaviors to them:
animateApp.animation('.myElement', function(){
return {
enter : function(element, done) {
//Do something on enter
},
leave : function(element, done) {
//Do something on leave
}
};
)};
Resulting in a product like this: http://embed.plnkr.co/uW4v9T/preview
I would like to do something similar with Angular 2.0 and I feel like I'm pretty close...
So here goes, I've created a simple router in the main application component that controls the navigation between the home and about components.
import { bootstrap, bind, Component, provide, View } from 'angular2/angular2';
import {RouteConfig, RouteParams, ROUTER_DIRECTIVES, ROUTER_PROVIDERS, APP_BASE_HREF, ROUTER_BINDINGS} from 'angular2/router'
/////////////////////////////////////////////////////////////////
// Home Component Start
/////////////////////////////////////////////////////////////////
@Component({
selector: 'home-cmp'
})
@View({
template: `
<h2 class="title">Home Page</h2>
`
})
class HomeCmp implements OnActivate, onDeactivate{
onActivate(next: ComponentInstruction, prev: ComponentInstruction) {
console.log("Home Page - initialized");
}
onDeactivate(next: ComponentInstruction, prev: ComponentInstruction) {
console.log("Home Page - destroyed");
}
}
/////////////////////////////////////////////////////////////////
// Home Component End
/////////////////////////////////////////////////////////////////
/////////////////////////////////////////////////////////////////
// About Component Start
/////////////////////////////////////////////////////////////////
@Component({
selector: 'about-cmp'
})
@View({
template: `
<h2 class="title">About Page</h2>
`
})
class AboutCmp implements OnActivate, onDeactivate {
onActivate(next: ComponentInstruction, prev: ComponentInstruction) {
console.log("About Page - initialized");
}
onDeactivate(next: ComponentInstruction, prev: ComponentInstruction) {
console.log("About Page - destroyed");
}
}
/////////////////////////////////////////////////////////////////
// About Component End
/////////////////////////////////////////////////////////////////
/////////////////////////////////////////////////////////////////
// Main Application Componenent Start
/////////////////////////////////////////////////////////////////
@Component({
selector: 'my-app'
})
@View({
template: `
<div>
<h1>Hello {{message}}!</h1>
<a [router-link]="['./HomeCmp']">home</a>
<a [router-link]="['./AboutCmp']">about</a>
<hr>
<router-outlet></router-outlet>
</div>
`,
directives: [ROUTER_DIRECTIVES]
})
@RouteConfig([
{path: '/', component: HomeCmp, as: 'HomeCmp'},
{path: '/about', component: AboutCmp, as: 'AboutCmp'}
])
export class App {
}
/////////////////////////////////////////////////////////////////
// Main Application Componenent End
/////////////////////////////////////////////////////////////////
bootstrap(App, [
ROUTER_BINDINGS,
ROUTER_PROVIDERS,
ROUTER_DIRECTIVES,
provide(APP_BASE_HREF, {useValue: '/'})
])
At the moment I am able to capture when the router has instantiated or destroyed a particular component when it moves from one to the next. This is great, but when the the previous component is destroyed I am not able to apply an on leave transition animation before the next component is initialized.
class HomeCmp implements OnActivate, onDeactivate{
onActivate(next: ComponentInstruction, prev: ComponentInstruction) {
//This works
TweenMax.fromTo($(".title"), 1, {opacity: 0}, {opacity: 1});
}
onDeactivate(next: ComponentInstruction, prev: ComponentInstruction) {
//This get ignored
TweenMax.fromTo($(".title"), 1, {opacity: 0}, {opacity: 1});
}
}
It seems like there is a solution to this using promises. Angular.io's API preview they state:
If onDeactivate returns a promise, the route change will wait until the promise settles.
and
If onActivate returns a promise, the route change will wait until the promise settles to instantiate and activate child components.
https://angular.io/docs/ts/latest/api/
I am super brand new to promises so I mashed this together into my code which solved the problem of my current component being destroyed on initialization of the next one, but then it never gets destroyed, it only creates a new instance of it. Every time I navigate back to it, it will create a new instance resulting in multiple copies.
onDeactivate(next: ComponentInstruction, prev: ComponentInstruction) {
function ani(){
TweenMax.fromTo($(".title"), 1, {opacity: 1}, {opacity: 0});
}
var aniPromise = ani();
aniPromise.then(function (ani) {
ani();
});
}
So to recap, the router should be able to wait for the current component to finish it's business before destroying it and initializing the next component.
I hope that all makes sense and I really appreciate the help!
As you quoted from the docs, if any of this hooks returns a Promise it will wait until its completed to move to the next one, so you can easily return a Promise that basically does nothing and wait a second (or as many time as you need).
onActivate(next: ComponentInstruction, prev: ComponentInstruction) {
TweenMax.fromTo($(".title"), 1, {opacity: 0}, {opacity: 1});
return new Promise((res, rej) => setTimeout(() => res(1), 1000));
}
onDeactivate(next: ComponentInstruction, prev: ComponentInstruction) {
TweenMax.fromTo($(".title"), 1, {opacity:1}, {opacity: 0});
return new Promise((res, rej) => setTimeout(() => res(1), 1000));
}
Note that I'm returning a Promise which runs a setTimeout. We wait a second to give the animation time enough to be completed.
I don't really like using setTimeouts, so we can use Observables as well, that personally I like the best.
return Rx.Observable.of(true).delay(1000).toPromise();
Here I'm passing a random value (true in this case) and delay it a second and finally cast it to Promise. Yes, it ends up being a Promise but I don't use it directly.
Here's a plnkr with an example working (expecting to be what you are looking for).
PS: If sometimes it complains about that it can't find a path to Rx, just keep refreshing until it works (I added Rx.js manually and it's a little heavy for plnkr apprently).
Angular 2 final solution:
plunk
In a nutshell, we can to use the @routeAnimation
built-in directive to achieve this. Each of our components representing a child route will be decorated with something like:
@Component({
selector: 'app-pageone'
host: { '[@routeAnimation]': 'true' },
styles: [':host { width: 300px; display: block; position: absolute; }']
animations: [
trigger('routeAnimation', [
state('*', style({transform: 'translateX(0)', opacity: 1})),
transition('void => *', [
style({transform: 'translateX(-100%)', opacity: 0}),
animate('0.5s cubic-bezier(0.215, 0.610, 0.355, 1.000)')
]),
transition('* => void',
animate('0.5s cubic-bezier(0.215, 0.610, 0.355, 1.000)', style({
transform: 'translateX(100%)',
opacity: 0
}))
)
])
]
})
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With