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.
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])
})
}
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
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
];
Below are the changes I made.
products
and need to go to product-details
so we go one step back ../
to product-dashboard
and then we navigate to product-details
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,
}
);
});
}
Second, we need to listen for the tab selection only on the parent product-dashboard
we can remove the code from product-details
I have added router params subscription, since it will dynamic and will show the proper value even when the component is not destroyed!
code
this.subscription.add(
this.route.params.subscribe((params: Params) => {
this.getProduct(+params['id']); // we get id as string, so we convert to number
})
);
ngOnDestroy
this will prevent memory leaks in your application!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 });
}
}