angulartwitter-bootstrapbootstrap-5primengbootstrap-tabs

Making tab active in angular from parent component


This is my products dashboard.html

   <nav #nav>
    <div class="nav nav-tabs" id="nav-tab" role="tablist">
      <button class="nav-link active" id="nav-categories-tab" data-bs-toggle="tab" data-bs-target="#nav-categories" type="button" role="tab" aria-controls="nav-categories" aria-selected="true" (click)="showCategories()" [ngClass]="{ 'active':activeTab==='categories'}">Categories</button>     
      <button class="nav-link" id="nav-product-lists-tab" data-bs-toggle="tab" data-bs-target="#nav-product-lists" type="button" role="tab" aria-controls="nav-product-lists" aria-selected="false"(click)="showProducts()" [ngClass]="{ 'active':activeTab==='products'}">Products</button>
      <button class="nav-link" id="nav-product-details-tab" data-bs-toggle="tab" data-bs-target="#nav-product-details" type="button" role="tab" aria-controls="nav-product-details" aria-selected="false" (click)="showProductDetails()" [ngClass]="{ 'active':activeTab==='product-details'}">Product Details</button>
     </div>
  </nav>

This is my products dashboard component

    export class ProductsDashboardComponent {
    activeTab = 'categories';
    constructor(private router:Router, private route: ActivatedRoute){}
    showCategories(){
      this.router.navigate(['categories'],{relativeTo: this.route})
    }
    showProducts(){
      this.router.navigate(['products'],{relativeTo: this.route})
    }
    showProductDetails(){
      this.activeTab = 'product-details';
      this.router.navigate(['product-details'],{relativeTo: this.route})
    }
  }

Now when I click product details tab the previous tab which is products tab also shown as active as shown below. So, how do I make the other tabs class inactive.

enter image description here

products.html

 <div class="container">
    <div class="list row ms-3">
        <div class="col-md-12">
            @if(hidden){
                <p-table [value]="products" styleClass="p-datatable-striped">
                    <ng-template pTemplate="header">
                        <tr>
                            <th>Name</th>
                            <th>Image</th>
                            <th>Category</th>
                            <th>Price</th>
                            <th>Actions</th>
                        </tr>
                    </ng-template>
                    <ng-template pTemplate="body" let-product>
                        <tr>
                            <td>{{product.title}}</td>
                            <td><img [src]="product.image" [alt]="product.title" width="100" class="shadow-4" /></td>
                            <td>{{product.category}}</td>
                            <td>{{product.price | currency:'USD'}}</td>
                            <td><a class="btn btn-outline-primary" (click)="getProductDetailsById(product,'product-details')">View</a></td>
                            <td><a class="btn btn-info" (click)="getProductById(product)">Edit</a></td>
                            <td><button class="btn btn-danger" (click)="deleteProduct(product)"> Delete</button></td>
                            </tr>
                    </ng-template>
                    <ng-template pTemplate="summary">
                        <div class="flex align-items-center justify-content-between">
                            In total there are {{products ? products.length : 0 }} products.
                        </div>
                    </ng-template>
                </p-table>
     </div>
  </div>
</div>

products component

 getProductDetailsById(product: Products,name:string){
    this.productService.get(product.id)
      .subscribe( data => {
       this.currentProduct=data;
       this.currentIndex = data["id"],
       this.parent.activeTab = name;
       this.router.navigate(['products-dashboard/product-details/',this.currentIndex])
      })
}

enter image description here

Now If I click view button then it should navigate to product-details tab

product-details.html

  <div class="container">
  <div class="row ms-3">
    <div class="card" style="width: 18rem;">
      <img src={{currentProduct.image}} class="card-img-top" alt={{currentProduct.title}}>
      <div class="card-body">
        <h5 class="card-title">{{currentProduct.title}}</h5>
        <h3 class="card-title">{{currentProduct.price | currency:'USD'}}</h3>
        <p class="card-text">{{currentProduct.description}}</p>
      </div>
    </div>
  </div>
</div>

product-details component

import { Component, Inject, Input, OnInit, ViewChild } from '@angular/core';
import {ActivatedRoute, NavigationEnd, Router, RouterLink } from '@angular/router';
import { FormsModule } from '@angular/forms';
import { ReactiveFormsModule } from '@angular/forms';
import { CommonModule } from '@angular/common';
import { Products } from '../models/products.model';
import {ProductService } from '../services/products.service';
import { ProductsDashboardComponent } from '../products-dashboard/products-dashboard.component';
import { filter } from 'rxjs';
@Component({
  selector: 'app-product-details',
  standalone: true,
  imports: [FormsModule, CommonModule,ReactiveFormsModule, RouterLink],
  providers:[ProductService],
  templateUrl: './product-details.component.html',
  styleUrl: './product-details.component.css'
})
export class ProductDetailsComponent implements OnInit{
  
   @Input() currentProduct: Products = {
     title: '',
     image: '',
     category:'',
     price: ''
   };
   nav:any;
   message = '';
   activeTab = 'categories';
   constructor(
    private router:Router, private route: ActivatedRoute,
     private productService: ProductService,
     @Inject(ProductsDashboardComponent) private parent: ProductsDashboardComponent) {}

   ngOnInit(): void {
       this.message = '';
       this.router.events.pipe(
        filter(event => event instanceof NavigationEnd)
      ).subscribe(() => {
        this.parent.activeTab = this.route?.snapshot?.firstChild?.routeConfig?.path || '';
      })
       this.getProduct(this.route.snapshot.params["id"]);
  }

   getProduct(id: string): void {
     this.productService.get(id)
       .subscribe({
         next: (data) => {
           this.currentProduct = data;
           console.log(data);
           this.currentProduct.id = data["id"]
           this.currentProduct.title = data["title"]
           this.currentProduct.category = data["category"]
           this.currentProduct.image = data["image"]
           this.currentProduct.price = data["price"]
           this.currentProduct.description = data["description"]
         },
         error: (e) => console.error(e)
       });
   }
}

Now the product shows in the same tab itself

enter image description here

app.routes.ts

export const routes: Routes = [
    { path: '',   redirectTo: '/home', pathMatch: 'full' }, // redirect to `first-component`
    { path: 'home', component: HomeComponent}, 
    { path: 'hooks', component: HooksComponent}, 
    { path: 'dashboard', component: DashboardComponent}, 
    { path: 'products-dashboard', component: ProductsDashboardComponent,
      children:[
        {path: 'categories', component: CategoriesComponent},
        {path: 'products', component: ProductsComponent},
        {path: 'product-details', component: ProductDetailsComponent},
        {path: "product-details/:id", component: ProductDetailsComponent },
      ]
    },
    { path: '**', component: PagenotfoundComponent },  // Wildcard route for a 404 page
  ];

Solution

  • Below are the changes I made.

    code

    getProductDetailsById(product: any, name: string) {
        this.productService.get(product.id).subscribe((data: any) => {
          this.router.navigate(
            [
              '../product-details', // we need to navigate one step back and then inside product details!
              data['id'],
            ],
            {
              relativeTo: this.activateRoute,
            }
          );
        });
      }
    

    code

     this.subscription.add(
      this.route.params.subscribe((params: Params) => {
        this.getProduct(+params['id']); // we get id as string, so we convert to number
      })
    );
    

    I hope this solves your issue!

    products html

    <div class="container">
      <div class="list row ms-3">
        <div class="col-md-12">
          @if(!hidden){
          <p-table [value]="products" styleClass="p-datatable-striped">
            <ng-template pTemplate="header">
              <tr>
                <th>Name</th>
                <th>Image</th>
                <th>Category</th>
                <th>Price</th>
                <th>Actions</th>
              </tr>
            </ng-template>
            <ng-template pTemplate="body" let-product>
              <tr>
                <td>{{ product.title }}</td>
                <td>
                  <img
                    [src]="product.image"
                    [alt]="product.title"
                    width="100"
                    class="shadow-4"
                  />
                </td>
                <td>{{ product.category }}</td>
                <td>{{ product.price | currency: 'USD' }}</td>
                <td>
                  <a
                    class="btn btn-outline-primary"
                    (click)="getProductDetailsById(product, 'product-details')"
                    >View</a
                  >
                </td>
                <td>
                  <a class="btn btn-info" (click)="getProductById(product)">Edit</a>
                </td>
                <td>
                  <button class="btn btn-danger" (click)="deleteProduct(product)">
                    Delete
                  </button>
                </td>
              </tr>
            </ng-template>
            <ng-template pTemplate="summary">
              <div class="flex align-items-center justify-content-between">
                In total there are {{ products ? products.length : 0 }} products.
              </div>
            </ng-template>
          </p-table>
          }
        </div>
      </div>
    </div>
    

    products ts

    import { CommonModule } from '@angular/common';
    import { Component, OnInit } from '@angular/core';
    import { RouterModule, Router, ActivatedRoute } from '@angular/router';
    import { TableModule } from 'primeng/table';
    import { of, Subscription } from 'rxjs';
    import { ProductService } from '../product.service';
    @Component({
      selector: 'app-products',
      templateUrl: './products.component.html',
      styleUrls: ['./products.component.css'],
      imports: [RouterModule, TableModule, CommonModule],
      standalone: true,
    })
    export class ProductsComponent implements OnInit {
      subscription: Subscription = new Subscription();
      hidden = false;
    
      get products() {
        return this.productService.products;
      }
    
      constructor(
        private router: Router,
        private productService: ProductService,
        private activateRoute: ActivatedRoute
      ) {}
    
      ngOnInit() {}
    
      getProductDetailsById(product: any, name: string) {
        this.subscription.add(
          this.productService.get(product.id).subscribe((data: any) => {
            this.router.navigate(
              [
                '../product-details', // we need to navigate one step back and then inside product details!
                data['id'],
              ],
              {
                relativeTo: this.activateRoute,
              }
            );
          })
        );
      }
    
      getProductById(e: any) {}
    
      deleteProduct(e: any) {}
    
      ngOnDestroy() {
        this.subscription.unsubscribe();
      }
    }
    

    product details html

    <div class="container">
      <div class="row ms-3">
        <div class="card" style="width: 18rem;">
          <img
            src="{{ currentProduct.image }}"
            class="card-img-top"
            alt="{{ currentProduct.title }}"
          />
          <div class="card-body">
            <h5 class="card-title">{{ currentProduct.title }}</h5>
            <h3 class="card-title">{{ currentProduct.price | currency: 'USD' }}</h3>
            <p class="card-text">{{ currentProduct.description }}</p>
          </div>
        </div>
      </div>
    </div>
    

    product details ts

    import { CommonModule } from '@angular/common';
    import { Component, Inject, Input, OnInit } from '@angular/core';
    import { FormsModule, ReactiveFormsModule } from '@angular/forms';
    import {
      ActivatedRoute,
      RouterLink,
      NavigationEnd,
      Router,
      Params,
    } from '@angular/router';
    import { filter, Subscription } from 'rxjs';
    import { ProductService } from '../product.service';
    
    @Component({
      selector: 'app-product-details',
      templateUrl: './product-details.component.html',
      styleUrls: ['./product-details.component.css'],
      imports: [FormsModule, CommonModule, ReactiveFormsModule, RouterLink],
      standalone: true,
    })
    export class ProductDetailsComponent {
      subscription: Subscription = new Subscription();
      @Input() currentProduct: any = {
        title: '',
        image: '',
        category: '',
        price: '',
      };
      nav: any;
      message = '';
      constructor(
        private router: Router,
        private route: ActivatedRoute,
        private productService: ProductService
      ) {}
    
      ngOnInit(): void {
        this.message = '';
        this.subscription.add(
          this.route.params.subscribe((params: Params) => {
            this.getProduct(+params['id']); // we get id as string, so we convert to number
          })
        );
      }
    
      getProduct(id: number): void {
        this.subscription.add(
          this.productService.get(id).subscribe({
            next: (data: any) => {
              this.currentProduct = data;
              this.currentProduct.id = data['id'];
              this.currentProduct.title = data['title'];
              this.currentProduct.category = data['category'];
              this.currentProduct.image = data['image'];
              this.currentProduct.price = data['price'];
              this.currentProduct.description = data['description'];
            },
            error: (e: any) => console.error(e),
          })
        );
      }
    
      ngOnDestroy() {
        this.subscription.unsubscribe();
      }
    }
    

    product dashboard html

    <nav #nav>
      <div class="nav nav-tabs" id="nav-tab" role="tablist">
        <button class="nav-link" id="nav-categories-tab" data-bs-toggle="tab" data-bs-target="#nav-categories" type="button" role="tab" aria-controls="nav-categories" aria-selected="true" (click)="showCategories()" [ngClass]="{ 'active':activeTab==='categories'}">Categories</button>     
        <button class="nav-link" id="nav-product-lists-tab" data-bs-toggle="tab" data-bs-target="#nav-product-lists" type="button" role="tab" aria-controls="nav-product-lists" aria-selected="false" (click)="showProducts()" [ngClass]="{ 'active':activeTab==='products'}">Products</button>
        <button class="nav-link" id="nav-product-details-tab" data-bs-toggle="tab" data-bs-target="#nav-product-details" type="button" role="tab" aria-controls="nav-product-details" aria-selected="false" (click)="showProductDetails()" [ngClass]="{ 'active':activeTab==='product-details'}">Product Details</button>
       </div>
       <router-outlet/>
    </nav>
    

    product dashboard ts

    import { CommonModule } from '@angular/common';
    import { Component, OnInit } from '@angular/core';
    import {
      ActivatedRoute,
      provideRouter,
      Router,
      RouterModule,
      NavigationEnd,
      Routes,
    } from '@angular/router';
    import { filter } from 'rxjs/operators';
    import { ProductService } from '../product.service';
    
    @Component({
      selector: 'app-products-dashboard',
      templateUrl: './products-dashboard.component.html',
      styleUrls: ['./products-dashboard.component.css'],
      imports: [CommonModule, RouterModule],
      providers: [ProductService],
      standalone: true,
    })
    export class ProductsDashboardComponent {
      activeTab = 'categories';
      constructor(private router: Router, private route: ActivatedRoute) {
        this.router.events
          .pipe(filter((event) => event instanceof NavigationEnd))
          .subscribe(() => {
            this.activeTab =
              this.route?.snapshot?.firstChild?.routeConfig?.path?.split('/')[0] ||
              '';
          });
      }
      showCategories() {
        this.router.navigate(['categories'], { relativeTo: this.route });
      }
      showProducts() {
        this.router.navigate(['products'], { relativeTo: this.route });
      }
      showProductDetails() {
        this.router.navigate(['product-details'], { relativeTo: this.route });
      }
    }
    

    stackblitz