typescript - Animații de tranziție a paginilor cu router-ul Angular 2.0 și promisiunile interfeței componentelor
În Angular 1.x putem folosi ngAnimate pentru a detecta când părăsim sau intrăm într-o anumită rută. Mai mult, suntem capabili să aplicăm comportamente asupra lor:
animateApp.animation('.myElement', function(){
return {
enter : function(element, done) {
//Do something on enter
},
leave : function(element, done) {
//Do something on leave
}
};
)};
Rezultând într-un produs ca acesta: http://embed.plnkr.co/uW4v9T/preview
Aș vrea să fac ceva similar cu Angular 2.0 și simt că sunt destul de aproape...
Așadar, iată, am creat un router simplu în componenta principală a aplicației care controlează navigarea între componentele home și about.
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: '/'})
])
În acest moment, pot să detectez când routerul a instanțiat sau a distrus un anumit component atunci când trece de la unul la altul. Acesta este grozav, dar atunci când componenta anterioră este distrusă, nu pot aplica o animație de tranziție la plecare înainte ca următorul component să fie inițializat.
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});
}
}
Se pare că există o soluție pentru aceasta folosind promisiuni. În previzualizarea API-ului Angular.io ei afirmă:
Dacă onDeactivate returnează o promisiune, schimbarea rutei va aștepta până când promisiunea se rezolvă.
și
Dacă onActivate returnează o promisiune, schimbarea rutei va aștepta până ce promisiunea se finalizează pentru a instanția și activa componentele copil.
https://angular.io/docs/ts/latest/api/
Sunt super nou în privința promisiunilor, așa că am pus asta împreună în codul meu, ceea ce a rezolvat problema componentei mele actuale care era distrusă la inițializarea următoarei, dar apoi ea niciodată nu este distrusă, doar creează o nouă instanță a ei. De fiecare dată când revin la ea, va crea o nouă instanță, rezultând mai multe copii.
onDeactivate(next: ComponentInstruction, prev: ComponentInstruction) {
function ani(){
TweenMax.fromTo($(".title"), 1, {opacity: 1}, {opacity: 0});
}
var aniPromise = ani();
aniPromise.then(function (ani) {
ani();
});
}
Deci, pentru a rezuma, routerul ar trebui să poată aștepta ca componenta curentă să își termine activitatea înainte de a o distruge și a inițializa următoarea componentă.
Sper că totul are sens și apreciez cu adevărat ajutorul!
Răspuns #1
Așa cum ai citat din documentație, dacă oricare dintre aceste hook-uri returnează un Promise, acesta va aștepta până este completat pentru a trece la următorul, așa că poți returna cu ușurință un Promise care practic nu face nimic și să aștepți o secundă (sau cât timp ai nevoie).
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));
}
Rețineți că returnez un Promise care rulează un setTimeout. Așteptăm o secundă pentru a oferi animației suficient timp pentru a fi finalizată.
Nu îmi place foarte mult să folosesc setTimeouts, așa că putem folosi și Observables, care personal îmi plac cel mai mult.
return Rx.Observable.of(true).delay(1000).toPromise();
Aici transmit o valoare aleatoare (true în acest caz), o întârzii o secundă și în final o transform într-un Promise. Da, ajunge să fie un Promise, dar nu îl folosesc direct.
Iată un plnkr cu un exemplu funcțional (se așteaptă să fie ceea ce cauți).
PS: Dacă uneori se plânge că nu poate găsi un drum către Rx, pur și simplu reîmprospătează până funcționează (am adăugat Rx.js manual și se pare că este puțin greu pentru plnkr).
Răspunsul #2
Soluția finală Angular 2:
plonk
Pe scurt, putem folosi directiva încorporată @routeAnimation pentru a realiza acest lucru. Fiecare dintre componentele noastre care reprezintă o rută copil va fi decorată cu ceva de genul:
@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
}))
)
])
]
})
