0

I'm working on a quiz app in Angular 9, and I'm trying to add animation in between question routes, for example: question/1, question/2, etc. Right now the animation works at the beginning of the first question and in between transition between last question and results. Doesn't seem to work when I click the previous or next buttons in the DIQuizComponent. Not sure how to go about fixing it. Please could you see my code below.

router-animations.ts:

import { trigger, transition, group, query, style, animate } from '@angular/animations';

export class RouterAnimations {
  static routeSlide =
    trigger('routeSlide', [
      transition('* <=> *', [
        group([
          query(':enter', [
            style({transform: 'translateX({{offsetEnter}}%)'}),
            animate('0.4s ease-in-out', style({transform: 'translateX(0%)'}))
          ], {optional: true}),
          query(':leave', [
            style({transform: 'translateX(0%)'}),
            animate('0.4s ease-in-out', style({transform: 'translateX({{offsetLeave}}%)'}))
          ], {optional: true}),
        ])
      ]),
    ]);
}

quiz-routing.module.ts:

import { NgModule } from '@angular/core';
import { RouterModule, Routes } from '@angular/router';

import { IntroductionComponent } from '../containers/introduction/introduction.component';
import { DependencyInjectionQuizComponent } from '../containers/dependency-injection-quiz/dependency-injection-quiz.component';
import { ResultsComponent } from '../containers/results/results.component';

const routes: Routes = [
  { path: '', redirectTo: 'intro', pathMatch: 'full' },
  { path: 'intro', component: IntroductionComponent, pathMatch: 'full' },
  { path: 'question', component: DependencyInjectionQuizComponent, pathMatch: 'full' },
  { path: 'question/:questionIndex', component: DependencyInjectionQuizComponent, pathMatch: 'full' },
  { path: 'results', component: ResultsComponent, pathMatch: 'full' }
];

@NgModule({
  imports: [RouterModule.forRoot(routes)],
  exports: [RouterModule]
})
export class QuizRoutingModule {}

app.component.html:

<main class="container grid-sm">
  <router-outlet></router-outlet>
</main>

route-reuse-strategy.ts:

import { ActivatedRouteSnapshot, DetachedRouteHandle, RouteReuseStrategy } from '@angular/router';

import { DependencyInjectionQuizComponent } from '../containers/dependency-injection-quiz/dependency-injection-quiz.component';


export class CustomReuseStrategy implements RouteReuseStrategy {
  handlers: {[key: string]: DetachedRouteHandle} = {};

  shouldDetach(route: ActivatedRouteSnapshot): boolean {
    return false;
  }

  store(route: ActivatedRouteSnapshot, handle: DetachedRouteHandle): void {
    this.handlers[route.routeConfig.path] = null;
  }

  shouldAttach(route: ActivatedRouteSnapshot): boolean {
    return !!route.routeConfig && !!this.handlers[route.routeConfig.path];
  }

  retrieve(route: ActivatedRouteSnapshot): DetachedRouteHandle {
    if (!route.routeConfig) {
      return null;
    }
    return this.handlers[route.routeConfig.path];
  }

  shouldReuseRoute(future: ActivatedRouteSnapshot, curr: ActivatedRouteSnapshot): boolean {
    return curr.component !== DependencyInjectionQuizComponent;
  }
}

inside di-quiz.component.ts:

  private setupRouting() {
    this.prev$ = this.questionChange$
      .pipe(
        map(questionIndex => questionIndex === 0 ? questionIndex : questionIndex - 1),
        share()
      );
    this.next$ = this.questionChange$
      .pipe(
        map(questionIndex => questionIndex === this.questions.length - 1 ? questionIndex : questionIndex + 1),
        share()
      );

    this.routeTrigger$ = this.questionChange$
      .pipe(
        startWith(0),
        pairwise(),
        map(([prev, curr]) => ({
          value: curr,
          params: {
            offsetEnter: prev > curr ? 100 : -100,
            offsetLeave: prev > curr ? -100 : 100
          }
        })),
      );
  }

in di-quiz.component.html template:

<mat-card [@routeSlide]="routeTrigger$ | async">
...
  <mat-card-footer>
    <section class="paging">
      <mat-card-actions>
        <div
          class="prev-question-nav"
          *ngIf="question && questionIndex > 1">
          <button
            type="button"
            mat-raised-button
            (click)="prevQuestion()"
            [routerLink]="prev$ | async"
            [class.disabled]="(questionChange$ | async) === 0">
            <strong>&laquo; Previous</strong>
          </button>
        </div>

        <div
          class="next-question-nav"
          *ngIf="question && questionIndex !== totalQuestions">
          <button
            type="button"
            mat-raised-button
            (click)="nextQuestion()"
            [routerLink]="next$ | async"
            [class.disabled]="(questionChange$ | async) === questions.length - 1">
            <strong>Next &raquo;</strong>
          </button>
        </div>

        <div
          class="show-score-nav"
          *ngIf="question && questionIndex === totalQuestions">
          <button type="button" mat-raised-button (click)="results()"
                  disableRipple="true" class="btn btn-outline-primary">
            <strong>Show Your Score</strong>
          </button>
        </div>
      </mat-card-actions>
    </section>
</mat-card>

questions-routing.service.ts:

import { Injectable } from '@angular/core';
import { BehaviorSubject } from 'rxjs';

@Injectable({ providedIn: 'root' })
export class QuestionsRoutingService {
  questionChange$ = new BehaviorSubject<number>(0);
}
integral100x
  • 332
  • 6
  • 20
  • 47

1 Answers1

1

I've prepared a small demo with animation enabled before navigation to the new route https://stackblitz.com/edit/angular-7uu97i.

The idea is that you start the animation, wait for it to be completed, and navigate to the next route. You can use a demo as example.

I hope it helps.

Dmitrii Makarov
  • 757
  • 3
  • 11
  • Thanks! I made the changes to my StackBlitz and the animation works when I click the next button (put the change on the mat-card), but it doesn't seem to navigate to the next question. Please can you look at my StackBlitz: https://stackblitz.com/edit/angular-9-quiz-app. Thank you! – integral100x Apr 27 '20 at 15:38
  • It navigates for a second and then the screen is blank (route is just "/question"). – integral100x Apr 27 '20 at 15:39
  • I commented out "this.lastClickedRoute = question;" and the animation works between the first and second questions, but not between second and third and so on. – integral100x Apr 27 '20 at 16:01
  • refactor animationDoneHandler(). Keep only `this.animationState$.next('none');` there. No need to check lastClickedRoute because you don't assign value to it – Dmitrii Makarov Apr 27 '20 at 16:13
  • For some reason, the correctAnswersCount doesn't seem to increment now when correct answer is chosen. – integral100x Apr 27 '20 at 16:38
  • Sorry one more question regarding the animation. It seems to be working fine when I select incorrect answers, but after the first question when I select a correct answer, it doesn't seem to animate in between questions. Would you happen to know why? – integral100x Apr 29 '20 at 00:06
  • Code has some bugs. Component QuizQuestionComponent has `@Output() answer`. You set it to null inside `setSelected()` - it is forbidden. Because `radioChange(answer: number)` cannot emit its value no more. After that the next answer is opened without clicking on the "Next" button. But animation is triggered inside the button click handler. So move into this direction – Dmitrii Makarov Apr 29 '20 at 03:52
  • Okay, got it! Thanks again! – integral100x Apr 29 '20 at 14:51