Animaciones de transición de página con enrutador Angular 2.0 y promesas de interfaz de componentes


En Angular 1.x podemos usar ngAnimate para detectar cuando estamos saliendo o entrando en una ruta en particular. Además somos capaces de aplicar comportamientos a ellos:

animateApp.animation('.myElement', function(){

    return {

        enter : function(element, done) {
            //Do something on enter
        },

        leave : function(element, done) {
            //Do something on leave
        }
    };

)};

Resulta en un producto como este: http://embed.plnkr.co/uW4v9T/preview

Me gustaría hacer algo similar con Angular 2.0 y siento que estoy bastante cerca...

Así que aquí va, he creado un enrutador simple en el componente principal de la aplicación que controla la navegación entre homey 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: '/'})
])

En este momento soy capaz de capturar cuando el router ha instanciado o destruido un componente en particular cuando se mueve de uno a otro. Esto es genial, pero cuando el componente anterior es destruido no puedo aplicar una animación de transición al salir antes de que el siguiente componente se inicialice.

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});
    }

}

Parece que hay una solución para esto usando promesas. Angular.io ' s API preview ellos dicen:

Si OnDeactivate devuelve una promesa, el cambio de ruta esperará hasta que la promesa se establezca.

Y

Si OnActivate devuelve una promesa, el cambio de ruta esperará hasta que la promesa se establezca para crear instancias y activar componentes secundarios.

Https://angular.io/docs/ts/latest/api /

Soy súper nuevo en promesas, así que mezclé esto en mi código que resolvió el problema de mi actual componente que se destruye al inicializar el siguiente, pero luego nunca se destruye, solo crea una nueva instancia del mismo. Cada vez que navegue de vuelta a ella, creará una nueva instancia que resultará en varias copias.

onDeactivate(next: ComponentInstruction, prev: ComponentInstruction) {

    function ani(){
      TweenMax.fromTo($(".title"), 1, {opacity: 1}, {opacity: 0});
    }

    var aniPromise = ani();

    aniPromise.then(function (ani) {
        ani();
    });

}

Así que para recapitular, el router debería ser capaz de esperar a que el componente actual termine su negocio antes de destruirlo e inicializar el siguiente componente.

Espero que todo tenga sentido y realmente aprecio el ¡Socorro!

Author: Sangwin Gawande, 2015-11-05

2 answers

Como ha citado de los documentos, si alguno de estos hooks devuelve una Promesa, esperará hasta que se complete para pasar al siguiente, por lo que puede devolver fácilmente una Promesa que básicamente no hace nada y esperar un segundo (o el tiempo que necesite).

 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));
  }

Tenga en cuenta que estoy devolviendo una Promesa que ejecuta un setTimeout. Esperamos un segundo para dar el tiempo de animación suficiente para ser completado.

Realmente no me gusta usar setTimeouts, por lo que también podemos usar Observables, que personalmente me gusta.

return Rx.Observable.of(true).delay(1000).toPromise();

Aquí estoy pasando un valor aleatorio (verdadero en este caso) y retrasarlo un segundo y finalmente lanzarlo a Promise. Sí, termina siendo una promesa pero no la uso directamente.

Aquí hay un plnkr con un ejemplo funcionando (esperando ser lo que estás buscando).

PD: Si a veces se queja de que no puede encontrar una ruta a Rx, solo mantén la actualización hasta que funcione (agregué Rx.js manualmente y es un poco pesado para plnkr apprently).

 13
Author: Eric Martinez,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/ajaxhispano.com/template/agent.layouts/content.php on line 61
2015-11-05 21:17:32

Angular 2 solución final:

Plunk

En pocas palabras, podemos usar la directiva incorporada @routeAnimation para lograr esto. Cada uno de nuestros componentes que representan una ruta infantil estará decorado con algo como:

@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
        }))
      )
    ])
  ]
})
 9
Author: Stephen Paul,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/ajaxhispano.com/template/agent.layouts/content.php on line 61
2017-04-27 10:30:21