I was trying to create an item counter for Wishlist Entries in my header area and tried to apply the solutions suggested here (for Cart Items) Change shopping-cart count in header in Angular
Unfortunately this does not quite cut it for me since the solutions are ignoring that there might be initial Data served as an Observable. In my case I'm storing the WishlistObjects with StorageMap (@ngx-pwa/local-storage), and that data is only returned as an Observable and I'm not quite sure how to associate that initial Data with the BehaviorSubject in the Service that is supposed to keep track of modifications. That's why I came up with this idea of having two seperate observables, that I combine...
COMPONENT:
export class WishlistLinkComponent{
merged$: Observable<number> = combineLatest([this.wishListService.getWishList(),this.wishListService.getCartCount()]).pipe(
map(([wishlist, count]) => {
console.log('wishlist: ',wishlist);
console.log('count: ',count);
return count ?? (wishlist.entries?.length ?? 0);
})
)
constructor(protected wishListService: WishListService) {}
}
SERVICE:
export class EmvAnonymousWishListService {
constructor(
private storage: StorageMap,
private productService: ProductService
) { }
private cartSubj:BehaviorSubject<number | undefined> = new BehaviorSubject<number| undefined>(undefined);
getCartCount(): Observable<number> {
return this.cartSubj.asObservable();
}
getWishList(): Observable<Cart> {
return this.storage.get('wishlist')
.pipe(
switchMap((productCodes: string[]) => this.productService.getProducts(productCodes)),
);
}
addEntry(productCode: string): void {
this.this.storage.get('wishlist').subscribe(productCodes => {
productCodes.push(productCode);
this.cartSubj.next(productCodes.length)
//store Logic...
});
}
removeEntry(productCode: string): void {
this.this.storage.get('wishlist').subscribe(productCodes => {
const filteredProductCodes = productCodes.filter((code: string) => {
return code !== productCode;
});
this.cartSubj.next(productCodes.length)
//store Logic...
});
}
}
I know this is ugly, but I don't know how to handle a scenario like this, with only one observable covering everything.
you can streamline it like:
Use ReplaySubject: Instead of BehaviorSubject, you can use ReplaySubject(1) to ensure the initial state from StorageMap is captured and updates are properly emitted.
Ensure Initial Storage Data: You want to initialize the cartSubj with the current state of the wishlist, so it can start with the correct count before any modifications are made.
// Service
export class EmvAnonymousWishListService {
constructor(
private storage: StorageMap,
private productService: ProductService
) {
this.initializeCartCount();
}
private cartSubj: ReplaySubject<number> = new ReplaySubject<number>(1);
// Initialize cart count based on stored wishlist
private initializeCartCount() {
this.storage.get<string[]>('wishlist').pipe(
map(productCodes => productCodes?.length ?? 0)
).subscribe(count => {
this.cartSubj.next(count);
});
}
getCartCount(): Observable<number> {
return this.cartSubj.asObservable();
}
getWishList(): Observable<Cart> {
return this.storage.get('wishlist').pipe(
switchMap((productCodes: string[]) => this.productService.getProducts(productCodes))
);
}
addEntry(productCode: string): void {
this.storage.get<string[]>('wishlist').subscribe(productCodes => {
const updatedCodes = productCodes ? [...productCodes, productCode] : [productCode];
this.storage.set('wishlist', updatedCodes).subscribe(() => {
this.cartSubj.next(updatedCodes.length);
});
});
}
removeEntry(productCode: string): void {
this.storage.get<string[]>('wishlist').subscribe(productCodes => {
const updatedCodes = productCodes?.filter(code => code !== productCode) || [];
this.storage.set('wishlist', updatedCodes).subscribe(() => {
this.cartSubj.next(updatedCodes.length);
});
});
}
}
// Component
export class WishlistLinkComponent {
merged$: Observable<number>;
constructor(protected wishListService: EmvAnonymousWishListService) {
this.merged$ = combineLatest([
this.wishListService.getWishList(),
this.wishListService.getCartCount()
]).pipe(
map(([wishlist, count]) => {
console.log('wishlist: ', wishlist);
console.log('count: ', count);
return count ?? (wishlist.entries?.length ?? 0);
})
);
}
}
Key improvements: ReplaySubject(1): This replaces BehaviorSubject to store the latest emitted value, even if no subscriber was present during the emission.
Initial Count: initializeCartCount ensures the wishlist count is correctly initialized from storage.
Updates after Add/Remove: After adding or removing entries, the updated count is pushed to cartSubj, and subscribers will get the updated count.