
Implementing the resolve route guard
The Resolve guard allows us to prefetch the data for a workout. In our case, what we want to do is use Resolve to check the validity of any ID that is passed for an existing workout. Specifically, we will run a check on that ID by making a call to the WorkoutBuilderService to retrieve the Workout Plan and see if it exists. If it exists, we will load the data associated with the Workout Plan so that it is available to the WorkoutComponent; if not we will redirect back to the Workouts screen.
Copy workout.resolver.ts from the workout-builder/workout folder under trainer/src/app/workout in checkpoint 4.5 and you will see the following code:
import 'rxjs/add/operator/map';
import 'rxjs/add/operator/take';
import { Injectable } from '@angular/core';
import { Observable } from 'rxjs/Observable';
import { Router, Resolve, RouterStateSnapshot,
ActivatedRouteSnapshot } from '@angular/router';
import { WorkoutPlan } from '../../core/model';
import { WorkoutBuilderService } from '../builder-services/workout-builder.service';
@Injectable()
export class WorkoutResolver implements Resolve<WorkoutPlan> {
public workout: WorkoutPlan;
constructor(
public workoutBuilderService: WorkoutBuilderService,
public router: Router) {}
resolve(
route: ActivatedRouteSnapshot,
state: RouterStateSnapshot): WorkoutPlan {
let workoutName = route.paramMap.get('id');
if (!workoutName) {
workoutName = '';
}
this.workout = this.workoutBuilderService.startBuilding(workoutName);
if (this.workout) {
return this.workout;
} else { // workoutName not found
this.router.navigate(['/builder/workouts']);
return null;
}
}
}
As you can see, the WorkoutResolver is an injectable class that implements the Resolve interface. The code injects the WorkoutBuilderService and Router into the class and implements the interface with the resolve method. The resolve method accepts two parameters; ActivatedRouteSnapshot and RouterStateSnapshot. In this case, we are only interested in the first of these two parameters, ActivatedRouteSnapshot. It contains a paramMap from which we extract the ID parameter for the route.
The resolve method then calls the startBuilding method of WorkoutBuildingService using the parameter supplied in the route. If the workout exists, then resolve returns the data and the navigation proceeds; if not, it re-routes the user to the workouts page and returns false. If new is passed as an ID, WorkoutBuilderService will load a new workout and the Resolve guard will allow navigation to proceed to the WorkoutComponent.
The resolve method can return a Promise , an Observable, or a synchronous value. If we return an Observable, we will need to make sure that the Observable completes before proceeding with navigation. In this case, however, we are making a synchronous call to a local in-memory data store, so we are just returning a value.
To complete the implementation of the WorkoutResolver, first make sure to import and add it to WorkoutBuilderModule as a provider:
....
import { WorkoutResolver } from './workout/workout.resolver';
@NgModule({
....
providers: [WorkoutBuilderService, WorkoutResolver]
})
....
Then, add it to the route configuration for WorkoutComponent by updating workout-builder-routing.module.ts as follows:
....
import { WorkoutResolver } from './workout/workout.resolver';
....
const routes: Routes = [
{
path: '',
component: WorkoutBuilderComponent,
children: [
{path: '', pathMatch: 'full', redirectTo: 'workouts'},
{path: 'workouts', component: WorkoutsComponent },
{path: 'workout/new', component: WorkoutComponent, resolve: { workout: WorkoutResolver} },
{path: 'workout/:id', component: WorkoutComponent, resolve: { workout: WorkoutResolver} },
{path: 'exercises', component: ExercisesComponent},
{path: 'exercise/new', component: ExerciseComponent },
{path: 'exercise/:id', component: ExerciseComponent }
]
},
];
As you can see, we add WorkoutResolver to the routing module's imports. Then, we add resolve { workout: WorkoutResolver } to the end of the route configuration for workout/new and workout/:id . This instructs the router to use the WorkoutResolver resolve method and assign its return value to workout in the route's data. This configuration means that WorkoutResolver will be called prior to the router navigating to WorkoutComponent and that the workout data will be available to the WorkoutComponent when it loads. We'll see how to extract this data in the WorkoutComponent in the next section.