10

I have read this article about Router transition Animations for Angular:

https://medium.com/google-developer-experts/angular-supercharge-your-router-transitions-using-new-animation-features-v4-3-3eb341ede6c8

And:

Angular 2 "slide in animation" of a routed component

However, this is too static. I want it to slide left and right depending on the order of the tab.

Is it possible to create router animations for this? Example of what I mean is below:

https://material.angular.io/components/tabs/examples

Look how it slides BOTH left and right very naturally depending on what tab you are on.

This has to be dynamic, because the tabs will be added at runtime.

Dolan
  • 1,519
  • 4
  • 16
  • 36

3 Answers3

9

Today things are a bit simpler because new animation aliases exist as :increment and :decrement. Aliases have been introduced in Angular 5.

So my modified solution is:

    @Component({
      selector: 'app-workspace-container',
      templateUrl: './workspace-container.component.html',
      styleUrls: ['./workspace-container.component.scss'],
      animations: [
        trigger('animRoutes', [
          transition(':increment', right),
          transition(':decrement', left),
        ]),
      ],
  })
  export class ComponentContainingRouterOutlet implements OnDestroy, OnInit {
    //... ngOnInit,ngOnDestroy

    constructor( private route: ActivatedRoute ) { }

    animationState: number;

    onActivate($event) {
      this.animationState = this.route.firstChild.snapshot.data['routeIdx'];
    }
  }

Call animation at router-outlet position:

<div [@animRoutes]="animationState">
  <router-outlet (activate)="onActivate($event)"></router-outlet>
</div>

modify routes definition as example, look at data: { routeIdx: X } :

    const routes: Routes = [
      {
        path: 'routeOne',
        component: ComponentOne,
        data: { routeIdx: 0 }
      },
      {
        path: 'routeTwo',
        component: ComponentTwo,
        data: { routeIdx: 1}
      },
      {
        path: 'routeThree',
        component: ComponentThree,
        data: { routeIdx: 2 }
      },
      {
        path: 'routeFour',
        component: ComponentFour,
        data: { routeIdx: 3 }
      },
      {
        path: '',
        redirectTo: 'routeOne',
        pathMatch: 'full'
      }
    ]

And transitions are the same as in Dolan's post:

const left = [
    query(':enter, :leave', style({ position: 'fixed', width: '100%' }), { optional: true }),
    group([
        query(':enter', [style({ transform: 'translateX(-100%)' }), animate('.3s ease-out', style({ transform: 'translateX(0%)' }))], {
            optional: true,
        }),
        query(':leave', [style({ transform: 'translateX(0%)' }), animate('.3s ease-out', style({ transform: 'translateX(100%)' }))], {
            optional: true,
        }),
    ]),
];

const right = [
    query(':enter, :leave', style({ position: 'fixed', width: '100%' }), { optional: true }),
    group([
        query(':enter', [style({ transform: 'translateX(100%)' }), animate('.3s ease-out', style({ transform: 'translateX(0%)' }))], {
            optional: true,
        }),
        query(':leave', [style({ transform: 'translateX(0%)' }), animate('.3s ease-out', style({ transform: 'translateX(-100%)' }))], {
            optional: true,
        }),
    ]),
];
Michal.S
  • 501
  • 6
  • 12
  • If I have a route from 1 -> 2, and a route from 3 -> 2, the only option is to set 2 to something above 1 and 3, e.g. 20 right? My approach was to compare the new route url with the history stack, and this way decide the animation. But it exposed as really complex and requires a lot more code than your approach. – btx Aug 19 '18 at 19:03
  • Left and right transitions are in [Dolan's post](https://stackoverflow.com/a/49590255/6804292). I skipped it intentionaly. – Michal.S Mar 14 '19 at 09:17
  • Say it in your post. Help viewers – Leasye Mar 14 '19 at 15:34
  • @Michal.S, Works perfect. Thank you for this solution – Asaf Agranat Aug 25 '19 at 10:52
  • The problem with this transition is that it resets the scroll on the page you are leaving, thus when navigating back (back button or `location.back()`) you will end up on the top of the page. – CularBytes Jun 18 '20 at 10:13
2

I have managed to get this to work by "faking" the state it is in.

In the component.html:

<div [@animRoutes]="pageState">
   <router-outlet></router-outlet>
</div>

pageState is a variable in the component.ts file.

Whenever I click on a tab which I want to go right, I will set pageState to right, and same for left, and let Angular take over the rest.

Note: You have to create a right and right1 state as a hack, because Angular currently does not support right => right state transitions!! Same applies to left of course.

My @Component annotation is below:

@Component({
    selector: 'app-workspace-container',
    templateUrl: './workspace-container.component.html',
    styleUrls: ['./workspace-container.component.scss'],
    animations: [
        trigger('animRoutes', [
            transition('* => right', right),
            transition('* => left', left),
            transition('* => right1', right),
            transition('* => left1', left),
        ]),
    ],
    changeDetection: ChangeDetectionStrategy.OnPush,
})

Where left, left1, right, right1 are:

const left = [
    query(':enter, :leave', style({ position: 'fixed', width: '100%' }), { optional: true }),
    group([
        query(':enter', [style({ transform: 'translateX(-100%)' }), animate('.3s ease-out', style({ transform: 'translateX(0%)' }))], {
            optional: true,
        }),
        query(':leave', [style({ transform: 'translateX(0%)' }), animate('.3s ease-out', style({ transform: 'translateX(100%)' }))], {
            optional: true,
        }),
    ]),
];

const right = [
    query(':enter, :leave', style({ position: 'fixed', width: '100%' }), { optional: true }),
    group([
        query(':enter', [style({ transform: 'translateX(100%)' }), animate('.3s ease-out', style({ transform: 'translateX(0%)' }))], {
            optional: true,
        }),
        query(':leave', [style({ transform: 'translateX(0%)' }), animate('.3s ease-out', style({ transform: 'translateX(-100%)' }))], {
            optional: true,
        }),
    ]),
];

TL;DR: Make the state you are going to into a variable, so you can dynamically set the state which you wish you are going to.

Dolan
  • 1,519
  • 4
  • 16
  • 36
  • what if i am keeping single animation array and changing it dynamically like: transition('* => *', currentAnimation) and I am updating currentAnimation value when I want to change animation type? And what is ChangeDetectionStrategy.OnPush? – No one May 08 '18 at 12:41
  • @Dolan what about today? with Angular 6? could you update your answer please? – tatsu Jun 11 '18 at 15:33
  • Yes it is still relevant in Angular 6, because Angular 5 to 6 does not break any of this – Dolan Jun 20 '18 at 16:44
0

Great solution from Michal.S. So I can use this also without Routechange but also on your own tab component.

If you have a custom tab control you can do it like this:

Step 1: Add the animation above

const left = [
  query(':enter, :leave', style({ position: 'absolute', width: '100%' }), { optional: true }), // or fixed
  group([
    query(':enter', [style({ transform: 'translateX(-100%)' }), animate('.4s ease-out', style({ transform: 'translateX(0%)' }))], {
      optional: true,
    }),
    query(':leave', [style({ transform: 'translateX(0%)' }), animate('.4s ease-out', style({ transform: 'translateX(100%)' }))], {
      optional: true,
    }),
  ]),
];

const right = [
  query(':enter, :leave', style({ position: 'absolute', width: '100%' }), { optional: true }),
  group([
    query(':enter', [style({ transform: 'translateX(100%)' }), animate('.4s ease-out', style({ transform: 'translateX(0%)' }))], {
      optional: true,
    }),
    query(':leave', [style({ transform: 'translateX(0%)' }), animate('.4s ease-out', style({ transform: 'translateX(-100%)' }))], {
      optional: true,
    }),
  ]),
];

Step 2: In the template add the trigger to the container of the animation items. Relative or flex positioned.

<div [@animStep]="currentStep">
    <app-step1 *ngIf=“currentStep === 1”></app-step1>
    <app-step2 *ngIf=“currentStep === 2”></app-step2>
    <app-step3 *ngIf=“currentStep === 3”></app-step3>
</div>

Step 3: Add the animation inside the Component decorator

// component
animations: [
  trigger('animStep', [
    transition(':increment', right),
    transition(':decrement', left),
  ]),
],

Step 3: Trigger change of the value

currentStep += 1;
Ron Jonk
  • 706
  • 6
  • 16