Dolan
Dolan

Reputation: 1647

Angular router transition animations slide both left and right conditionally

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.

Upvotes: 12

Views: 11798

Answers (3)

Ron Jonk
Ron Jonk

Reputation: 899

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;

Upvotes: 0

Michal.S
Michal.S

Reputation: 531

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

Upvotes: 11

Dolan
Dolan

Reputation: 1647

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.

Upvotes: 3

Related Questions