I want to update my heart icon, which bound to every product, to add to favourites.
<i class="fa-regular fa-heart" *ngIf="!isFavourite"></i>
<i class="fa-solid fa-heart" *ngIf="isFavourite"></i>
To do it I fetch data from the database using a backend request in the service, same with toggling, the backend logic works, the only problem is that I have to refresh the page every time to see the heart icon toggled.
import { Injectable } from '@angular/core';
import { HttpClient } from '@angular/common/http';
import { environment } from '../../../../environments/environment';
import { Observable, BehaviorSubject } from 'rxjs';
import { PrimitiveProduct } from '../models/product.model';
@Injectable({
providedIn: 'root'
})
export class FavouriteService {
apiUrl = `${environment.apiUrl}/user/favourites`;
private favouritesSubject = new BehaviorSubject<PrimitiveProduct[]>([]);
favourites$ = this.favouritesSubject.asObservable();
constructor(private http: HttpClient) {}
fetchFavourites(): void {
this.http
.get<PrimitiveProduct[]>(this.apiUrl, { withCredentials: true })
.subscribe({
next: (favourites) => {
this.favouritesSubject.next(favourites);
},
error: (err) => console.error('Error fetching favourites:', err)
});
}
toggleFavourite(productUid: string): Observable<string> {
return this.http.post<string>(
`${this.apiUrl}?productUid=${productUid}`,
null,
{ withCredentials: true }
);
}
// edited
updateFavouritesLocally(productUid: string) {
const favourites = this.favouritesSubject.getValue();
const isAlreadyFavorite = favourites.some((fav) => fav.uid === productUid);
if (isAlreadyFavorite) {
this.favouritesSubject.next(
favourites.filter((fav) => fav.uid !== productUid)
);
} else {
this.productService.getProductByUid(productUid).subscribe({
next: (fullProduct) => {
if (fullProduct) {
this.favouritesSubject.next([...favourites, fullProduct]);
} else {
console.error(`Product with uid ${productUid} not found`);
}
},
error: (err) => {
console.error(`Error fetching product with uid ${productUid}:`, err);
}
});
}
}
}
This is how I use the toggleFavourite() in product component:
ngOnInit() {
this.favouriteService.favourites$.subscribe((favourites) => {
this.isFavourite = favourites.some((fav) => fav.uid === this.product.uid);
});
}
toggleFavourite(event: Event) {
event.stopPropagation();
this.favouriteService.toggleFavourite(this.product.uid).subscribe({
next: () => {
this.favouriteService.updateFavouritesLocally(this.product.uid);
},
error: (err) => {
console.error('Error toggling favourite:', err);
}
});
}
And here in favourites component I display the data in user account section:
export class FavouritesComponent implements OnInit {
favouriteProducts: PrimitiveProduct[] = [];
constructor(private favouriteService: FavouriteService) {}
ngOnInit() {
this.favouriteService.fetchFavourites();
this.favouriteService.favourites$.subscribe((products) => {
this.favouriteProducts = products;
});
}
}
I tried the approach with BehaviorSubject
, but still I have to refresh page to see the result. Overall adding products to favourites (wishlist) works fine, I just have problem with this annoying refreshing. Should I use something like localStorage or something? Looks like a simple task, but still don't know how to do it.
Heres a couple of free pointers:
<i [class]="'fa-heart ' + isFavourite ? 'fa-regular' : 'fa-solid'"></i>
On to your problem: Your toggleFavourite
function is too complex. There are 2 ways to solve this problem.
You are sort of doing both.
I'm going to assume you are trying to do number 2. In your toggleFavourite
function move the call to updateFavouritesLocally
out of your subscription. There is no need to wait for the toggle response because you are not using anything that is returned.
toggleFavourite(event: Event) {
event.stopPropagation();
this.favouriteService.updateFavouritesLocally(this.product.uid);
// Add `.pipe()` and use it to unsubscribe
this.favouriteService.toggleFavourite(this.product.uid).subscribe({
next: () => { /* empty */ },
error: (err) => {
console.error('Error toggling favourite:', err);
}
});
If this doesn't work then its likely in your updateFavouritesLocally
function. Which brings me to another note, this casting has me concerned
const newFavourite: PrimitiveProduct = {
uid: productUid
} as PrimitiveProduct;
this.favouritesSubject.next([...favourites, newFavourite]);
In typescript you shouldn't have to cast like this. Either it is a PrimitiveProduct
because that's the correct signature or it isn't. Could the problem be somewhere in the missing data in this object? I can't say for sure without seeing the template code too.