DEV Community

GaurangDhorda
GaurangDhorda

Posted on

Angular Router set dynamically document Title of page, Protect Router with guard, and Breadcrumbs component

In this article, we are going to understand proper way...

  1. How one can use angular router ?
  2. How to protect angular router ?
  3. How to set dynamic document title when route page changes ?
  4. How to make Breadcrumbs component ?

In this project we are using Bootstrap and here I am not setting up content of bootstrap, I am assuming you have pre set up bootstrap component in angular project.

First of all create new angular project with routing option.

   ng new dynamictitle
Enter fullscreen mode Exit fullscreen mode

open app-routing.module.ts file, and adds routing path.


  const routes: Routes = [
  {
    path: "",
    component: MainComponent,
    data: { title: "Root Page", breadcrums: "Root" }
  },
  {
    path: "login",
    component: LoginComponent,
    data: { title: "Login Page", breadcrums: "Login" }
  },
  {
    path: "home",
    component: HomeComponent,
    data: { title: "Home Page", breadcrums: "Home" },
    children: [
      {
        path: "records",
        component: RecordsComponent,
        data: { title: "Home / Records Page", breadcrums: "Records" },
        children: [
          {
            path: "findrecords",
            component: HelloComponent,
            data: { title: "Find Records Page", breadcrums: "Find-Records" }
          }
        ]
      }
    ],
    canActivate: [ProtectRouteGuard]
  },
  {
    path: "about",
    component: AboutComponent,
    data: { title: "About Page", breadcrums: "About" },
    canActivate: [ProtectRouteGuard]
  }
];
Enter fullscreen mode Exit fullscreen mode

Now, we are adding some new properties above router file.

  1. data : { title: '', breadcrumbs: '' }, data object have two property, later in component tree we need this information when that route in changed. title is displaying over document title, and breadcrumbs are displaying inside root page.

  2. canActivate: [] , canActivate router guard protects our path, and we can not routed to this page without login to site or any restriction we are imposing based on user role.

  3. children: [], this is used to set inner path of page. so this are inner routed pages or we can say this routes are children of related parent. like Home => Records => Find-Records.

as per route path configuration, create related component using..

   ng generate component Main
Enter fullscreen mode Exit fullscreen mode

In above command just change for other components like..
Home, About, breadcrums, header, Login.

Now, generate child component of HomeComponent..

   ng g c home/records
Enter fullscreen mode Exit fullscreen mode

and inside records component generate find records component..

   ng g c home/records/hello 
Enter fullscreen mode Exit fullscreen mode

Now, we are protecting our path..

for that generate new service and guard inside authentication folder, if not then create new root level authentication folder. inside that folder generate service and guard.

   ng g s authentication
   ng g guard protect-guard
Enter fullscreen mode Exit fullscreen mode

Open authentication service...

   export class AuthenticationService {
  private isLoggedIn: BehaviorSubject<boolean> = new BehaviorSubject<boolean>(
    false
  );
  private isLoggedIn$ = this.isLoggedIn.asObservable();

  constructor(private router: Router) {}

  getIsUserLoggedIn(): Observable<boolean> {
    return this.isLoggedIn$;
  }
  setUserLoggedIn(loggedInStatus: boolean) {
    this.isLoggedIn.next(loggedInStatus);
    loggedInStatus
      ? this.router.navigate(["home"])
      : this.router.navigate(["login"]);
  }
}

Enter fullscreen mode Exit fullscreen mode

and inside auth-guard..

   export class ProtectRouteGuard implements CanActivate {
  constructor(
    private authService: AuthenticationService,
    private router: Router
  ) {}

  canActivate(
    next: ActivatedRouteSnapshot,
    state: RouterStateSnapshot
  ): Observable<boolean> | Promise<boolean> | boolean {
    return this.authService.getIsUserLoggedIn().pipe(
      take(1),
      map((isLoggedIn: boolean) => {
        if (!isLoggedIn) {
          this.router.navigate(["login"]);
          return false;
        }
        return true;
      })
    );
  }
}

Enter fullscreen mode Exit fullscreen mode

Now we provide protected-guard services inside app.module.ts

     providers: [ProtectRouteGuard]
Enter fullscreen mode Exit fullscreen mode

and inside app-routing.module.ts file we protect route using canActivate: [ProtectRouteGuard]

Upto now, we are done protecting our routes, now we moove to second part series.

PART 2 [SETTING DYNAMIC TITLE PATH, and Breadcrums]

First, generate new main service..

   ng g s main
Enter fullscreen mode Exit fullscreen mode

Then, inside main service...

   export class MainService {
  routerEventsTitle$: Observable<IUrlTitle>;
  breadCrumbs: IBreadCrums[] = [{ label: "", url: "" }];

  constructor(
    private title: GetSetPageTitleService,
    private router: Router,
    private activatedRouter: ActivatedRoute
  ) {
    this.routerEventsTitle$ = this.getSetRouterTitle();
  }

  setDefaultTitle(defaultTitle: string) {
    this.title.setCurrentTitle(defaultTitle);
  }

  getSetRouterTitle(): Observable<IUrlTitle> {
    return this.router.events.pipe(
      filter((event: RouterEvent) => event instanceof NavigationEnd),
      map((routeUrl: RouterEvent) => {
        let childRouter = this.activatedRouter.firstChild;
        while (childRouter.firstChild) {
          childRouter = childRouter.firstChild;
        }
        if (childRouter.snapshot.data["title"]) {
          let titleBreadCrums: IUrlTitle = {
            url: routeUrl.url,
            title: childRouter.snapshot.data["title"]
          };
          return titleBreadCrums;
        }
        return {
          url: routeUrl.url,
          title: this.title.getCurrentTitle()
        };
      }),
      map((titleUrl: IUrlTitle) => {
        this.breadCrumbs.length = 0;
        let menuItem = this.generateBreadCrums(this.activatedRouter.root);
        this.breadCrumbs.push(...menuItem);
        return { ...titleUrl, breadCrums: this.breadCrumbs };
      }),
      tap((currentTitle: IUrlTitle) => {
        // /this.breadCrumbs.push(currentTitle);
        this.title.setCurrentTitle(currentTitle.title);
        console.log("b ", this.breadCrumbs);
      })
    );
  }
  generateBreadCrums(
    activatedRouter: ActivatedRoute,
    url = "",
    breadcrumbs: IBreadCrums[] = [{ label: "", url: "" }]
  ): IBreadCrums[] {
    const children: ActivatedRoute[] = activatedRouter.children;

    if (children.length === 0) {
      return breadcrumbs;
    }

    for (const child of children) {
      const routeURL: string = child.snapshot.url
        .map(segment => segment.path)
        .join("/");
      if (routeURL !== "") {
        url += `/${routeURL}`;
      }

      console.log("url ", routeURL);
      const label = child.snapshot.data["breadcrums"];
      console.log("label ", label);
      if (label) {
        breadcrumbs.push({ label, url });
      }

      return this.generateBreadCrums(child, url, breadcrumbs);
    }
  }
}
Enter fullscreen mode Exit fullscreen mode

Now, Open app.component.html file

   <ng-container *ngIf="routerEventsTitle$| async as routerTitle">
    <app-header> </app-header>
    <app-breadcrums [modelItems]="routerTitle.breadCrums"> </app-breadcrums>
    <div class="container">
        <router-outlet></router-outlet>
    </div>
</ng-container>
Enter fullscreen mode Exit fullscreen mode

Now, Open app.component.ts file

   export class AppComponent {
  name = "Angular " + VERSION.major;
  appRootTitle = "Root Page";
  routerEventsTitle$: Observable<IUrlTitle>;

  constructor(private mainService: MainService) {
    this.mainService.setDefaultTitle(this.appRootTitle);
  }

  ngOnInit() {
    this.routerEventsTitle$ = this.mainService.routerEventsTitle$;
  }
}
Enter fullscreen mode Exit fullscreen mode

First of all we inject main-service inside app.component.ts, then we gets Observable and subscribe it inside component using async pipe. then inside template file we pass input to breadcrums component.

Now, generate breadcrums.template file

   <nav aria-label="breadcrumb">
    <ol class="breadcrumb">
        Your are at :
        <li *ngFor="let bc of items; let last = last" class="breadcrumb-item" aria-current="page" [class.active]="last">
            <a *ngIf="last !==true" [routerLink]="bc.url"> {{bc.label}} </a>
            <span *ngIf="last" > {{bc.label}} </span>
        </li>
    </ol>
</nav>
Enter fullscreen mode Exit fullscreen mode

Now, generate breadcrums.ts file

   export class BreadcrumsComponent implements OnInit {
  items;
  @Input("modelItems") set menu(item) {
    this.items = item;
  }
  constructor() {}

  ngOnInit() {}
}
Enter fullscreen mode Exit fullscreen mode

Complete working example is here

Discussion (0)