A newcomer to Angular here. I know similar questions have been asked before and I read too many of them here, answers about AJAX and async programming, but couldn't solve the issue. I want to see the details of the products when I click on 'Details' button, the routing does its job but no values are visible on the page other than static html. So here it is:
This is my details service to get specific details of a product
@Injectable()
export class DetailsService {
constructor(private http:HttpClient) { }
path = "http://localhost:3000/products";
getProductById(productId: any): Observable<Product> {
let newPath = this.path
if (productId) {
newPath += "?id=" + productId;
}
console.log(newPath);
return this.http.get<Product>(newPath).pipe(
tap(data => console.log(JSON.stringify(data) )),
catchError(this.handleError)
);
}
//handle error func here
This is the details component
export class DetailsComponent implements OnInit {
title = 'Product Details'
productId: any;
product: Product;
constructor(private detailsService: DetailsService,
private activatedRoute: ActivatedRoute,
private alertifyService: AlertifyService,
) {}
ngOnInit() {
this.activatedRoute.params.subscribe(params => {
this.productId = params["id"];
console.log(this.productId);
this.detailsService.getProductById(this.productId).subscribe(data => {
this.product = data;
console.log(this.product);
console.log('38')
});
console.log(this.productId + " " + typeof this.productId);
});
}
Everything works without any errors or something, but the attributes are not shown on details.component.html which is;
<div *ngIf="product">
<img class="listing-photo" [src]="product.imageUrl"
alt="Exterior photo of {{product.name}}"/>
<section class="listing-description">
<h2 class="listing-heading">{{product.name}}</h2>
<p class="listing-location">{{product.description}}, {{product.price}}</p>
</section>
<section class="listing-features">
<h2 class="section-heading">About this housing location</h2>
<ul>
<li>Units available: {{product.price}}</li>
<li>Does this location have wifi: {{product.price}}</li>
<li>Does this location have laundry: {{product.price}}</li>
</ul>
</section>
</div>
The call is made from product.component.html
//snippet with ngFor etc
<div class="card-footer">
<a (click)="addToCart(product)" class="btn btn-primary text-white">Add to cart</a>
<a [routerLink]="['/products',product.id]" class="btn btn-secondary ml-3 ">Details</a>
</div>
//the rest
I tried hundred things if two hundred and I'm lost now. I understand the nature of async programming but can't get it work as intended.
EDIT: Log of console.log(this.product) is as follows:
Array(1)
0:
{category: 'PC Components', categoryId: 1, description: 'Mechanical gaming keyboard with customizable RGB lighting.', id: '1', imageUrl: 'https://images.unsplash.com/photo-1612198188060-c7…xMjA3fDB8MHxwaG90by1wYWdlfHx8fGVufDB8fHx8fA%3D%3D', …}
length: 1
[[Prototype]]:
Array(0)
Since I have no idea what edit you made to the endpoint I'll keep the same you provided. Regarding my answer, this is the optimized version of your code with comments where needed:
@Injectable()
export class DetailsService {
private _http = inject(HttpClient); // We directly inject the HttpClient
private _path = "http://localhost:3000/products";
// Since you are checking if the productId is given, I made it optional with the ? symbol.
public getProductById(productId?: string): Observable<Product> {
// URL created dynamically using the ternary operator
return this.http.get<Product>(productId ? `${this._path}?id=${productId}` : this.path);
}
export class DetailsComponent implements OnInit {
// Protected because you are not inheriting this component into another.
protected title = 'Product Details';
protected product!: Product;
// Private since this variable isn't used in the HTML view.
private _productId!: string;
constructor(
private _detailsService: DetailsService,
private activatedRoute: ActivatedRoute,
) {}
public ngOnInit(): void {
this.activatedRoute.params.subscribe(params => {
this._productId = params["id"];
// By splitting the service call into a new function, the code will be easier to handle when dealing with large data.
this._getProductById(this._productId);
});
}
private _getProductById(productId: string): void {
this._detailsService.getProductById(productId).subscribe(data => {
this.product = data;
});
}
}
The code is actually fine, I'd just put a touch to it in order to not let the view explode if the product has not the data you are trying to display. We achieve this using the ?
key:
<div *ngIf="product">
<img class="listing-photo" [src]="product?.imageUrl"
alt="Exterior photo of {{product?.name}}"/>
<section class="listing-description">
<h2 class="listing-heading">{{product?.name}}</h2>
<p class="listing-location">{{product?.description}}, {{product?.price}}</p>
</section>
<section class="listing-features">
<h2 class="section-heading">About this housing location</h2>
<ul>
<li>Units available: {{product?.price}}</li>
<li>Does this location have wifi: {{product?.price}}</li>
<li>Does this location have laundry: {{product?.price}}</li>
</ul>
</section>
</div>
No need to do anything. I'm not a big fan on using routerLink
in the component's template, but that's up to you.
Code is not tested nor deeply verified for typos / misspell words, but it should give you a strong starting point, and a fresh codebase.
Few notes:
unknown
. A great article about it can be found hereProduct
is a class or an interface consider changing it to a type. It is much more better. To the why, there are a lot of articles that explain it in detail, but long story short:Types can be employed with primitive types like type string, whereas interfaces can't be used with primitive types. Types are generally more flexible and expressive.
If you need any help, feel free to ask.