Search code examples
angularangular-routingbreadcrumbsxng-breadcrumb

What is wrong with my breadcrumbs - Angular 11?


I'm trying to create breadcrumbs using XNG-Breadcurmb, in my Angular 11. I tried to understand the document and implement it based on my understanding.

enter image description here

As per the above GIF, I'm missing connection between 2nd and third page.

Expectation

Users List --> User Dashboard --> Completed List

When click on User Dashboard I should go back on User Dashboard page then User List

Current behavior

when i click on cards (either click on completed list btn or in-completed list btn), User Dashboard disappeared. i don't know where im missing.

Team, appologize for not adding stackblitz, due to some reason I'm blocked to access stackblitz or any code sandbox

so far i have

app.routing.ts

import { HomeComponent } from './components/home/home.component';
import { NgModule } from '@angular/core';
import { RouterModule, Routes } from '@angular/router';
const routes: Routes = [
    { path: '', pathMatch: 'full', redirectTo: '', component: HomeComponent },
    {
        path: 'home', component: HomeComponent,
        data: {
            breadcrumb: {
                label: 'my home',
                info: 'home',
                routeInterceptor: (routeLink) => {
                    console.log(routeLink);
                    return routeLink;
                },
            },
        },
    },
    {
        path: 'customers',
        loadChildren: () => import('./feature/customer/customer.module').then(m => m.CustomerModule),
        data: {
            breadcrumb: {
                label: 'Users List',
            },
        },
    }
];

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

customer.routing.module.ts

import { IncompleteUsersComponent } from './components/incomplete-users/incomplete-users.component';
import { CompletedUsersComponent } from './components/completed-users/completed-users.component';
import { CustomerTabSelectionComponent } from './components/customer-tab-selection/customer-tab-selection.component';
import { CustomersListComponent } from './components/customers-list/customers-list.component';
import { NgModule } from '@angular/core';
import { RouterModule, Routes } from '@angular/router';
import { CustomerDashboardComponent } from './components/customer-dashboard/customer-dashboard.component';

const routes: Routes = [
  {
    path: '', component: CustomersListComponent,
    /*  data: {
       breadcrumb: 'Customer List'
     } */
    data: {
      breadcrumb: {
        disable: true,
      },
    },
  },
  {
    path: 'dashboard/:id', component: CustomerDashboardComponent, data: {
      breadcrumb: 'User Dashboard'
    }
  },
  {
    path: 'user-tab/:id', component: CustomerTabSelectionComponent, data: {
      breadcrumb: 'Users tab'
    },
  },
  {
    path: 'cmp-users/:id', component: CompletedUsersComponent, data: {
      breadcrumb: 'Completed Users list'
    }
  },
  {
    path: 'icmp-users/:id', component: IncompleteUsersComponent, data: {
      breadcrumb: 'In-completed Users list'
    }
  }

];

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

Customer-list-component.ts

import { UsersService } from './../../users.service';
import { Component, OnInit } from '@angular/core';
import { Router } from '@angular/router';

@Component({
  selector: 'app-customers-list',
  templateUrl: './customers-list.component.html',
  styleUrls: ['./customers-list.component.scss']
})
export class CustomersListComponent implements OnInit {

  userList;
  constructor(private userService: UsersService, private router: Router) { }

  ngOnInit(): void {
    this.loadUsers();
  }

  loadUsers(): void {
    this.userService.getUsersList().subscribe((response) => {
      if (response) {
        this.removeDuplicateUsers(response);
      }
    }, err => {
      console.log('Error', err);
    });
  }

  removeDuplicateUsers(data): void {
    this.userList = data.filter((v, i, a) => a.findIndex(t => (t.userId === v.userId)) === i);
    console.log('user list', this.userList);
  }

  gotoUserDashboard(item): void{
    console.log('href clicked', item);
    this.router.navigate(['customers/dashboard/' + item.userId]);
  }

}

Customer-list.html

<div class="wrapper">
    <div class="lbl-container">
        <span class="lbl">User Id</span>
        <span class="lbl">Status</span>
    </div>
    <div class="data-container" *ngFor="let user of userList">        
        <span class="lbl"><a href="javascript:void(0);" (click)="gotoUserDashboard(user)">{{user.userId}}</a></span>      
        <span class="lbl">{{user.completed === false ? 'Not Complete': 'Completed'}}</span>
    </div> 
</div>

Customer-dashboard.ts

import { Component, OnInit } from '@angular/core';
import { ActivatedRoute, Router } from '@angular/router';
import { UsersService } from '../../users.service';

@Component({
  selector: 'app-customer-dashboard',
  templateUrl: './customer-dashboard.component.html',
  styleUrls: ['./customer-dashboard.component.scss']
})
export class CustomerDashboardComponent implements OnInit {
  totalComplete;
  totalInComplete;
  userInfo;
  constructor(private activatedRoute: ActivatedRoute, private router: Router, private userService: UsersService) {
    this.activatedRoute.params.subscribe(data => {
      const userId = data.id;
      this.getUserInfoByUserId(userId);
      console.log(userId);
    });
  }

  ngOnInit(): void {
  }

  getUserInfoByUserId(userId): void {
    console.log('user data', userId);
    this.userService.getUserByID(userId).subscribe((response) => {
      if (response) {
        console.log(response);
        this.userInfo = response;
        this.totalComplete = response.filter((t) => t.completed === true).length;
        this.totalInComplete = response.filter((f) => f.completed === false).length;
        console.log('totalComplete', this.totalComplete);
        console.log('total In-Complete', this.totalInComplete);
      }
    }, err => {
      console.log(err);
    });
  }

  gotoUserbyStatus(e): void{
    console.log('click', this.userInfo);
    console.log('event', e.target.value);
    const extra =  { tab: e.target.value, userId : this.userInfo[0].userId };
    // this.router.navigate(['customers/customer-tab/' + this.userInfo[0].userId], { state: extra });
    this.router.navigate(['customers/user-tab/' + this.userInfo[0].userId], { state: extra });
  }
}

customer.dashboard.html

<div class="task-wrapper" >
    <div class="completed">
        <h2>Completed</h2>
        <h4>{{totalComplete}}</h4>
        <div class="list">
            <button (click)="gotoUserbyStatus($event)" value="completeLst">Completed List</button>
        </div>
    </div>
    <div class="incompleted">
        <h2>In-Completed</h2>
        <h4>{{totalInComplete}}</h4>
        <div class="list">
            <button (click)="gotoUserbyStatus($event)" value="inCompleteLst">In-Completed List</button>
        </div>
    </div>
</div>

Customer-tab.ts

import { Component, OnInit } from '@angular/core';
import { ActivatedRoute, Router } from '@angular/router';
import { UsersService } from '../../users.service';

@Component({
  selector: 'app-customer-tab-selection',
  templateUrl: './customer-tab-selection.component.html',
  styleUrls: ['./customer-tab-selection.component.scss']
})
export class CustomerTabSelectionComponent implements OnInit {

  userId;
  tabFocus = '';
  constructor(private activatedRoute: ActivatedRoute, private router: Router, private userService: UsersService) {
    this.activatedRoute.params.subscribe(data => {
      if (data && data.id){
        const userId =  data.id;
        console.log('params', userId);
      }
    });
    const routerData = this.router.getCurrentNavigation().extras.state;
    console.log('extra', routerData);
    this.userId = routerData.userId;
    this.tabFocus = routerData.tab;
  }

  ngOnInit(): void {
  }

  onTabClick(event): void{
    console.log('tab click event', event.target.value);
    const focusTab = event.target.value;
    if (focusTab === 'completed'){
      // this.router.navigate(['customers/customer-tab/' + this.userInfo[0].userId], { state: extra });
      this.router.navigate(['customers/cmp-users/' + this.userId]);
    } else if (focusTab === 'incompleted'){
      this.router.navigate(['customers/icmp-users/' + this.userId]);
    }
  }
}

Customer-tab.html

<div class="container-fluid">
    <div class="row">
        <ul class="nav nav-tabs" id="myTab" role="tablist">
            <li class="nav-item" role="presentation">
                <button class="nav-link" id="completed-tab" data-bs-toggle="tab" data-bs-target="#completed"
                    type="button" role="tab" aria-controls="completed" aria-selected="true"
                    [ngClass]="[tabFocus === 'completeLst' ? 'active' : '']" (click)="onTabClick($event)"
                    value="completed">completed</button>
            </li>
            <li class="nav-item" role="presentation">
                <button class="nav-link" id="incompleted-tab" data-bs-toggle="tab" data-bs-target="#incompleted"
                    type="button" role="tab" aria-controls="incompleted" aria-selected="false"
                    [ngClass]="[tabFocus === 'inCompleteLst' ? 'active' : '']" (click)="onTabClick($event)"
                    value="incompleted">incompleted</button>
            </li>

        </ul>
        <div class="tab-content" id="myTabContent">
            <div class="tab-pane fade" id="completed" role="tabpanel" aria-labelledby="completed-tab"
                [ngClass]="[tabFocus === 'completeLst' ? 'show active' : '']">
                <app-completed-users></app-completed-users>
            </div>
            <div class="tab-pane fade" id="incompleted" role="tabpanel" aria-labelledby="incompleted-tab"
                [ngClass]="[tabFocus === 'inCompleteLst' ? 'show active' : '']">
                <app-incomplete-users></app-incomplete-users>
            </div>

        </div>
    </div>
</div>

And given a try with this native solution , but again i couldn't make it because i didn't get good understanding about router event(That's my fault).

So requesting all to help me,

Thanks


Solution

  • The library (by default) utilizes the routes hierarchy to set up the breadcrumbs. Here is a visualization of your routes hierarchy at the moment routes hierarchy

    As you can see, User Dashboard is neither a descendant of Completed List nor any module or component route.

    The simplest solution is to refactor CustomerDashboardComponent,CustomerTabSelectionComponent,CompletedUsersComponent, and IncompleteUsersComponent to be children of another component . You will end up with following routes structure:

    const routes: Routes = [
      {path: '', component: CustomersListComponent},
      {
        path: 'users', component: UsersComponent,
        data: { breadcrumb: 'User Dashboard' },
        children: [
          {path: '', redirectTo: 'dashboard/:id', pathMatch: 'exact'},
          {
            path: 'dashboard/:id', component: CustomerDashboardComponent
          },
          {
            path: 'user-tab/:id', component: CustomerTabSelectionComponent,
            data: { breadcrumb: 'Users tab' },
          },
          {
            path: 'cmp-users/:id', component: CompletedUsersComponent,
            data: { breadcrumb: 'Completed Users list' }
          },
          {
            path: 'icmp-users/:id', component: IncompleteUsersComponent,
            data: { breadcrumb: 'In-completed Users list' }
          }
        ]
      }
    
    ];
    

    Note: you don't need breadcrumb on any descendant with empty path if parent route path has one. If both are defined, the child (with empty path) will overwrite the parent one source