1

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?

user9847788
  • 2,135
  • 5
  • 31
  • 79
  • 2
    Try using resolvers to fetch data instead of fetching them inside component. And within the resolver, redirect to a particular route if no data is returned by api. – Nabin Paudyal Jun 25 '19 at 10:26
  • In your `getEmployee` method after fetch check if emplyee is there if so then use `displayEmployee` else redirect to page not found – jitender Jun 25 '19 at 10:31

3 Answers3

1

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.

Darren Ruane
  • 2,385
  • 1
  • 8
  • 17
0

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)
    );
}
jitender
  • 10,238
  • 1
  • 18
  • 44
  • I would not personally recommend this. Why should the `getEmployee()` method deal with route redirecting? Doing so violates the [Single Responsibility Principle](https://en.wikipedia.org/wiki/Single_responsibility_principle). This method should get an employee and do nothing else – Darren Ruane Jun 25 '19 at 12:56
0

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']);
}
Amila Thennakoon
  • 419
  • 1
  • 5
  • 15