How to guard route if HTTP GET request returns no

2019-08-09 04:26发布

Below is a table in my Angular app. It is populated with data from employees.json:

<tbody>
    <tr *ngFor="let employee of employees">
        <td (click)="viewEmployeeProfile(1, employee.id)">{{employee.fullName}}
        </td>
    </tr>
</tbody>

When the user clicks on a name, the employeeId is passed to this method:

viewEmployeeProfile(roleId: number, employeeId: number) {
    this._router.navigate(['/profile', roleId, employeeId]);
}

Here is the route in my AppRouting module:

const routes: Routes = [
  {
    path: 'profile/:role/:id',
    component: ProfileComponent,
    // canActivate: [RequireEmployeeProfileGuardGuard]
  },
  { 
    path: 'page-not-found', 
    component: PageNotFoundComponent 
  },
  { 
    path: '**', 
    component: PageNotFoundComponent
  }
];

Example path: http://localhost:4200/profile/1/4

When the user routes to theProfile component, this code is called:

profile.component.ts:

ngOnInit() {
    this.route.paramMap.subscribe(params => {
    const roleId = +params.get('role');
    const empId = +params.get('id');
    this.getEmployee(empId);
    });

}

getEmployee(id: number) {
    this.employeeService.getEmployee(id).subscribe(
      (employee: IEmployee) => this.displayEmployee(employee),
      (err: any) => console.log(err)
    );
}

displayEmployee(employee: IEmployee) {
    this.employee.fullName = employee.fullName;
}

profile.component.html:

<tr>
    <td><b>Full Name</b></td>
    <td>{{employee.fullName}}</td>
</tr>

And here is my employee.service:

baseUrl = 'http://localhost:3000/employees';

getEmployee(id: number): Observable<IEmployee> {
    return this.httpClient.get<IEmployee>(`${this.baseUrl}/${id}`)
        .pipe(catchError(this.handleError));
    }

This code is working fine, & displays data as expected.

Currently, if I navigate to a route such as http://localhost:4200/profile/1/123456789, where that employeeId does not exist, the Profilecomponent is displayed with no data.

Instead of this, I would want the user to be brought back to the PageNotFound component.

Here are my current routes:

const routes: Routes = [
  { path: 'profile/:role/:id', component: ProfileComponent },
  { path: '**', component: PageNotFoundComponent }
];

Can someone please tell me what changes I need to make to implement this?

3条回答
我命由我不由天
2楼-- · 2019-08-09 04:53

This is a perfect opportunity for the CanActivate guard.

Since Angular 7.1.0, route guards may now return a URLTree which gives us some really cool flexibility in our guards. Here is a nice article that goes over the changes and what they mean / how they can be used.

I would suggest you create your guard. Something like the following:

import { Injectable } from '@angular/core';
import { CanActivate, Router, UrlTree, ActivatedRouteSnapshot } from '@angular/router';
import { Observable, of } from 'rxjs';
import { map, catchError } from 'rxjs/operators';
// import your employee service here

@Injectable()
export class RequireEmployeeProfileGuard implements CanActivate {
  constructor(private router: Router, private employeeService: EmployeeService) {
  }

  canActivate(route: ActivatedRouteSnapshot): Observable<boolean | UrlTree> {
    return this.employeeService.getEmployee(+route.paramMap.get('id')).pipe(
      catchError(() => of(false)),
      map(employee => !!employee || this.router.parseUrl('page-not-found'))
    );
  }
}

From here, just go to your Routing Module, import this guard and add it to your route like this:

{ 
  path: 'profile/:role/:id', 
  component: ProfileComponent,
  canActivate: [RequireEmployeeProfileGuard]
}

I would also probably define an explicitly named route for the error component too like:

{ 
  path: 'page-not-found', 
  component: PageNotFoundComponent 
}

So then 'absolute-redirect-url-here' from the guard above would become 'page-not-found'.

Also, since you would still want to have a 'catch-all' case for invalid URLs, you would probably want a route like:

{ 
  path: '**', 
  redirectTo: 'page-not-found'
}

So how does this work?

It might look complex but it's very simple at its core really. The magic is in the canActivate() method that we added to the guard:

We are requesting the employee profile from the employeeService and converting it to a boolean using the double-not operator (basically, checking "do we have a matching employee profile"). If this converts to a false then the URLTree is returned which will redirect the router to the specified, absolute route.

When any route is being resolved, Angular will run through all the guards attached to it in a pre-defined order. If any of the guards 'fail' then the route won't be loaded.

查看更多
一夜七次
3楼-- · 2019-08-09 05:04

You can modify your getEmployee method to do so something like

getEmployee(id: number) {
    this.employeeService.getEmployee(id).subscribe(
      (employee: IEmployee) => {
         if(employee){
          this.displayEmployee(employee)
        }else{
          //redirect to page not found here
       }
      },
      (err: any) => console.log(err)
    );
}
查看更多
劳资没心,怎么记你
4楼-- · 2019-08-09 05:09

Add below routings to AppRouting module

{path: '404', component: NotFoundComponent},
{path: '**', redirectTo: '/404'}

The above route will catch incorrect routes at the root level and within any nested children as well.put this path as the last path in your routing module

call gotoHeroes method from the getEmployee method if records count is 0 or null

gotoHeroes() {
  this.router.navigate(['/404']);
}
查看更多
登录 后发表回答