8

The app I am working on requires the ability to navigate back and forth through the routes so when you navigate one way through the app in one direction the components animate from left to right, and when you navigate back they navigate from right to left.

i.e.

component1 -> component2 =  left -> right animation
component2 -> component1 = right -> left animation

I can achieve this easily enough in NG4 but the issue is that it doesn't navigate both the exit and entering components. The exiting component just disappears and the entering component animates in.

I can also achieve this in NG2 - but the complication arises when you have conditional animation. ie.

component2 -> component3 = left -> right animation
component2 -> component1 = right -> left animation

The problem here is triggering a different animation depending on the next or previous route.

Does anyone have a solution in either NG2 or 4 to solve the issue of animating both views simultaneously in either direction?

I haven't put this in plunkr because I have no idea how to set up an NG4 app in that. Apologies.

My NG4 solution is based on this demo app https://github.com/matsko/ng4-animations-preview

but essentially in my app.component.ts I have the following animation:

animations: [
    trigger('routerAnimations',[
        transition('page1 => page2',[
            style({transform: 'translateX(100%)'}),
            animate('500ms')
        ]),
        transition('page2 => page1', [
            style({transform: 'translateX(-100%)'}),
            animate('500ms')
        ]),
        transition('page2 => page3',[
            style({transform: 'translateX(100%)'}),
            animate('500ms')
        ]),
        transition('page3 => page2',[
            style({transform: 'translateX(-100%)'}),
            animate('500ms')
        ]),
        transition('page3 => page4',[
            style({transform: 'translateX(100%)'}),
            animate('500ms')
        ]),
        transition('page4 => page3',[
            style({transform: 'translateX(-100%)'}),
            animate('500ms')
        ])
    ])
]

and a function that grabs an animation value from each route and binds (?) it to the outlet variable on the <router-outlet> in app.component.html:

export class AppComponent {
    prepareRouteTransition(outlet) {
        let routeData: any;
        try {
            routeData = outlet['_activatedRoute'].snapshot.routeConfig['animation'];
       } catch(e) {
           return '';
       }
       return routeData.value;
    }
}

Function above grabs the animation value which is used in the animation to define the transition start and end values:

export const APP_ROUTES = [{
    path: '',
    component: Page1Component,
    animation: {
      value: 'page1',
    }
  }, 
  {
    path: 'page2',
    component: Page2Component,
    animation: {
      value: 'page2',
    }
  },
  {
    path: 'page3',
    component: Page3Component,
    animation: {
        value: 'page3'
    }
  },
  {
      path: 'page4',
      component: Page4Component,
      animation: {
          value: 'page4'
      }
  }
];

app.component.html:

<div class="page" [@routerAnimations]="prepareRouteTransition(outlet)">
  <router-outlet #outlet="outlet"></router-outlet>
</div>
br.julien
  • 3,420
  • 2
  • 23
  • 44
Clay
  • 478
  • 4
  • 10
  • Same problem here and I arrived to the same road block. Did you figured it out? – elecash Apr 26 '17 at 08:55
  • Thanks @Clay I've tested and it works very well :) However I'm going to wait to 4.1.0 since the implementation is cleaner but this should be the accepted answer for now – elecash Apr 27 '17 at 10:31

2 Answers2

8

You can achieve this now by defining animations that query for :enter and :leave elements.

This example will animate out the exiting route and animate in the entering route smoothly:

const slideLeft = [
  query(':leave', style({ position: 'absolute', left: 0, right: 0 ,transform: 'translate3d(0%,0,0)' })),
  query(':enter', style({ position: 'absolute', left: 0, right: 0, transform: 'translate3d(-100%,0,0)' })),
  group([
    query(':leave',
      animate('1s', style({ transform: 'translate3d(100%,0,0)' }))),
    query(':enter',
      animate('1s', style({ transform: 'translate3d(0%,0,0)' })))
  ])
]

const slideRight = [
  query(':leave', style({ position: 'absolute', left: 0, right: 0 , transform: 'translate3d(0%,0,0)'})),
  query(':enter', style({ position: 'absolute', left: 0, right: 0, transform: 'translate3d(100%,0,0)'})),

  group([
    query(':leave',
      animate('1s', style({ transform: 'translate3d(-100%,0,0)' }))),
    query(':enter', 
      animate('1s', style({ transform: 'translate3d(0%,0,0)' })))
  ])
]

@Component({
  selector: 'app-root',
  templateUrl: './app.component.html',
  styleUrls: ['./app.component.css'],
  animations: [
    trigger('routerAnimations', [
      transition('products => product-details', slideRight),
      transition('product-details => products', slideLeft)
    ])
  ]
})
SpaceFozzy
  • 2,728
  • 1
  • 19
  • 21
0

You can achieve this by putting your animation around the router-outelet you want to animate, not the view itself.

Also you want to do your animation in a group([]) which will have both animations run at the same time.

<!-- app.html -->
<div [@routeAnimation]="prepRouteState(routerOutlet)">
 <!-- make sure to keep the ="outlet" part -->
 <router-outlet #routerOutlet="outlet"></div>

 class AppComponent {
 prepRouteState(outlet: any) {
     return outlet.activatedRouteData['animation'] || 'firstPage'; 
   } 
 }

<!-- app.ts -->
const ROUTES = [
   { path: '',
     component: HomePageComponent,
     data: {
       animation: 'homePage'
     }
   },
   { path: 'support',
     component: SupportPageComponent,
     data: {
       animation: 'supportPage'
     }
   }
 ]

// the animation itself
trigger('routeAnimation', [
  //...
  transition('homePage => supportPage', [
    group([
      query(':enter', [
        style({ opacity: 0 }),
        animate('0.5s', style({ opacity: 1 })),
        animateChild()
      ]),
      query(':leave', [
        animate('0.5s', style({ opacity: 0 })),
        animateChild()
      ])
    ])
  ]),
  //...
])

as seen on YearofMoo Routable Animations

Zuriel
  • 1,848
  • 1
  • 19
  • 32
  • the function prepRouterState, rather than returning the outlet, you could do anything you want a a programatic level there and return your animation states. from there you can then do animations based on what you return, so like from 3 to 1 left or 1 to 3 right or 3 to 2 left, etc. you have to build a function that give you the correct state you want and the animation will work from there – Zuriel Sep 26 '18 at 01:18