Having recently delved into the world of Angular, I'm grappling with the challenge of passing data from a parent component to a child component. On my product listing page, clicking on a product should route to the product detail page, but I can't seem to get the product details to display.
In Component 1, all the product data is stored, whereas Component 2 is responsible for routing to a new page where the product data is displayed.
Within the product list (parent) component file:
import { Component, OnDestroy, OnInit } from "@angular/core";
import { Subscription } from "rxjs";
import { IProduct } from "./product";
import { ProductService } from "./product.service";
@Component({
templateUrl: './product-list.component.html',
styleUrls: ['./product-list.component.css']
})
export class ProductListComponent implements OnInit, OnDestroy {
pageTitle = 'Anthony\'s Product List';
imageWidth = 50;
imageMargin = 2;
showImage: boolean = true;
errorMessage: string = '';
sub!: Subscription;
private _listFilter: string = '';
get listFilter(): string {
return this._listFilter
}
set listFilter(value: string) {
this._listFilter = value;
console.log('In setter:', value);
this.filteredProducts = this.performFilter(value);
}
filteredProducts: IProduct[] = [];
products: IProduct[] = [];
//defining the injector so it can be used in this class
constructor(private productService: ProductService) {}
//this is a method
performFilter(filterBy: string): IProduct[] {
filterBy = filterBy.toLocaleLowerCase();
return this.products.filter((product: IProduct) =>
product.productName.toLocaleLowerCase().includes(filterBy));
}
toggleImage(): void {
this.showImage = !this.showImage;
}
//this lifecycle hook retrives the data we need
ngOnInit(): void {
this.productService.getProducts().subscribe({
next: products => {
this.products = products;
this.filteredProducts = this.products;
},
error: err => this.errorMessage = err
});
}
ngOnDestroy(): void {
//this.sub.unsubscribe();
}
onRatingClicked(message: string): void {
this.pageTitle = 'Product List: ' + message;
}
}
Parent component HTML structure:
<div class='card'>
<div class='card-header'>
{{pageTitle}}
</div>
<div class='card-body'>
<div class='row'>
<div class='col-md-2'>Filter by:</div>
<div class='col-md-4'>
<input type='text'
[(ngModel)]='listFilter' />
</div>
</div>
<div class='row'>
<div class='col-md-6'>
<h4>Filtered by: {{listFilter}} </h4>
</div>
</div>
<div class='table-responsive'>
<table class='table' *ngIf='products.length'>
<thead>
<tr>
<th>
<button class='btn btn-primary'
(click)='toggleImage()'>
{{showImage ? 'Hide' : 'Show'}} Image
</button>
</th>
<th>Product</th>
<th>Code</th>
<th>Available</th>
<th>Price</th>
<th>5 Star Rating</th>
</tr>
</thead>
<tbody>
<tr *ngFor='let product of filteredProducts'>
<td>
<img *ngIf='showImage'
[src]='product.imageUrl'
[title]='product.productName'
[style.width.px]='imageWidth'
[style.margin.px]='imageMargin'>
</td>
<td>
<a [routerLink]="['/products', product.productId]">
{{product.productName}}
</a>
</td>
<td>{{product.productCode | convertToSpaces:'-' }}</td>
<td>{{product.releaseDate}}</td>
<td>{{product.price | currency:'AUD'}}</td>
<td><pm-star [rating]='product.starRating'
(ratingClicked)='onRatingClicked($event)'>
</pm-star></td>
</tr>
</tbody>
</table>
</div>
</div>
</div>
The product detail component file:
import { Component, OnInit } from '@angular/core';
import { ActivatedRoute, Router } from '@angular/router';
import { IProduct } from '../product/product';
@Component({
templateUrl: './product-detail.component.html',
styleUrls: ['./product-detail.component.css']
})
export class ProductDetailComponent implements OnInit {
product: IProduct | undefined;
pageTitle: any = 'Product Detail';
constructor(private route:ActivatedRoute,
private router: Router) { }
ngOnInit(): void {
const id = Number(this.route.snapshot.paramMap.get('id'));
this.pageTitle += `: ${id}`;
}
onBack(): void {
this.router.navigate(['/products']);
}
}
Child component HTML structure:
<div class='card'>
<div class='card-header'>
{{pageTitle + ': ' + product?.productName}}
</div>
<br>
<div class='card-body'>
<div class='col-md-4'>Code:</div>
<div class='col-md-8'>{{product?.productCode}}</div>
</div>
<br>
<div class='card-footer'>
<button class='btn btn-outline-secondary'
style='width:80px'
(click)='onBack()'>
<i class='fa fa-chevron-left'></i> Back
</button>
</div>
</div>
A snippet of product data from a JSON file:
[
{
"productId": 1,
"productName": "Leaf Rake",
"productCode": "GDN-0011",
"releaseDate": "March 19, 2019",
"description": "Leaf rake with 48-inch wooden handle.",
"price": 19.95,
"starRating": 3.2,
"imageUrl": "assets/images/leaf_rake.png"
},
{
"productId": 2,
"productName": "Garden Cart",
"productCode": "GDN-0023",
"releaseDate": "March 18, 2019",
"description": "15 gallon capacity rolling garden cart",
"price": 32.99,
"starRating": 4.2,
"imageUrl": "assets/images/garden_cart.png"
},
The ProductService TypeScript file:
import { HttpClient, HttpErrorResponse } from "@angular/common/http";
import { Injectable } from "@angular/core";
import { catchError, Observable, tap, throwError } from "rxjs";
import { IProduct } from "./product";
@Injectable({
providedIn: 'root'
})
export class ProductService {
private productUrl = 'api/products/products.json';
constructor(private http: HttpClient) {}
getProducts(): Observable<IProduct[]> {
return this.http.get<IProduct[]>(this.productUrl).pipe(
tap(data => console.log('All: ', JSON.stringify(data))),
catchError(this.handleError)
);
}
I am just beginning my journey of learning, so please overlook any errors.