Вопрос или проблема
Я пытался создать счетчик элементов для записей в списке желаемого в своей области заголовка и пытался применить решения, предложенные здесь (для элементов корзины)
Изменить количество товаров в корзине в заголовке в Angular
К сожалению, это не совсем устраивает меня, поскольку решения игнорируют тот факт, что могут быть начальные данные, предоставляемые как Observable. В моем случае я храню объекты списка желаемого с помощью StorageMap (@ngx-pwa/local-storage), и эти данные возвращаются только как Observable, и я не совсем уверен, как связать эти начальные данные с BehaviorSubject в сервисе, который должен отслеживать изменения. Вот почему я придумал идею использовать два отдельных observable, которые я комбинирую…
КУПРОНТ:
export class WishlistLinkComponent{
merged$: Observable<number> = combineLatest([this.wishListService.getWishList(),this.wishListService.getCartCount()]).pipe(
map(([wishlist, count]) => {
console.log('список желаемого: ',wishlist);
console.log('количество: ',count);
return count ?? (wishlist.entries?.length ?? 0);
})
)
constructor(protected wishListService: WishListService) {}
}
СЕРВИС:
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)
//логика хранения...
});
}
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)
//логика хранения...
});
}
}
Я знаю, что это некрасиво, но я не знаю, как справиться со сценарием подобного рода, когда только один observable покрывает все.
вы можете упростить это так:
Используйте ReplaySubject: вместо BehaviorSubject вы можете использовать ReplaySubject(1), чтобы убедиться, что начальное состояние из StorageMap захватывается и обновления правильно передаются.
Убедитесь, что начальные данные хранятся: вы хотите инициализировать cartSubj с текущим состоянием списка желаемого, чтобы он мог начинаться с правильного количества до внесения любых изменений.
// Сервис
export class EmvAnonymousWishListService {
constructor(
private storage: StorageMap,
private productService: ProductService
) {
this.initializeCartCount();
}
private cartSubj: ReplaySubject<number> = new ReplaySubject<number>(1);
// Инициализация количества корзины на основе сохраненного списка желаемого
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);
});
});
}
}
// Компонент
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);
console.log('количество: ', count);
return count ?? (wishlist.entries?.length ?? 0);
})
);
}
}
Ключевые улучшения:
ReplaySubject(1): Это заменяет BehaviorSubject для хранения последнего переданного значения, даже если ни один подписчик не был присутствует во время передачи.
Начальное количество: initializeCartCount гарантирует, что количество списка желаемого правильно инициализируется из хранилища.
Обновления после добавления/удаления: после добавления или удаления записей обновленное количество передается в cartSubj, и подписчики получат обновленное количество.
Вы можете попробовать использовать tap
, чтобы обновить начальное значение поведения subject, и это позаботится о том, чтобы начальное значение было установлено.
getWishList(): Observable<Cart> {
return this.storage.get('wishlist')
.pipe(
switchMap((productCodes: string[]) => this.productService.getProducts(productCodes)),
tap((wishlist: any) => {
this.cartSubj.next(wishlist.entries?.length);
}
);
Компонент может прочитать эти данные и обновить их.
Я не уверен, в чем ваша проблема, но у меня сложилось впечатление, что все дело в подсчете, почему бы вам не воспользоваться оператором count из rxjs?
например:
of(1, 2, 3).pipe(count()).subscribe(console.log);
это выведет “3”
Если это вам не помогает, не могли бы вы подробнее рассказать, что именно вы ищете?
Ответ или решение
Чтобы инициализировать BehaviorSubject
со значением, полученным из Observable
, вам потребуется выполнить несколько шагов. В этом ответе мы рассмотрим, как это можно сделать с использованием Angular и RxJS, что особенно актуально для вашей задачи — отслеживания количества элементов в списке желаемого.
1. Предпосылки
Необходимо понимать, что BehaviorSubject
является видом Subject
, который требует начального значения и эмитирует это значение сразу при подписке. В вашей ситуации стартовое значение должно быть получено из Observable
, поэтому логика инициализации должна учитывать асинхронное получение данных.
2. Использование ReplaySubject
Один из вариантов — заменить BehaviorSubject
на ReplaySubject
с буфером 1. Это позволит сохранить последнее эмитированное значение и обеспечить его доступность для всех новых подписчиков, даже если они подписываются позже.
Вот как это можно реализовать:
// Сервис
export class EmvAnonymousWishListService {
private cartSubj: ReplaySubject<number> = new ReplaySubject<number>(1);
constructor(private storage: StorageMap, private productService: ProductService) {
this.initializeCartCount();
}
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<string[]>('wishlist').pipe(
switchMap(productCodes => 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);
});
});
}
}
3. Обновление компонента
Теперь компонент, который будет использоваться для отображения количества элементов в списке желаемого, может выглядеть следующим образом:
// Компонент
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);
})
);
}
}
4. Преимущества
Используя ReplaySubject
, вы гарантируете, что любое новое значение будет доступно для всех подписчиков, даже если они подписались позже. Это упростит управление состоянием вашего компонента и вписание логики обработки асинхронных запросов.
Заключение
Эта реализация позволит вам корректно инициализировать BehaviorSubject
, используя значения из Observable
, и успешно управлять изменениями в вашем списке желаемого. Если у вас возникли дополнительные вопросы, не стесняйтесь их задать.