#angular #typescript #rxjs
Вопрос:
Я пишу приложение для электронной коммерции, подобное приложению с angular 12 и rxjs.
Я создал сервис категорий, который извлекает категории graphql с сервера и анализирует их, чтобы они были легко доступны на остальной части веб-сайта.
проблема, с которой я сталкиваюсь, заключается в том, что если пользователь сразу заходит на страницу, которая использует сервис категорий, то он возвращает пустой ответ, потому что graphql еще не вернул значение.
поэтому я решил создать видимый объект, который можно наблюдать, и установить его в значение true, когда это будет сделано, чтобы другие функции знали, что он работает.
@Injectable({
providedIn: 'root'
})
export class ProductCategoriesService {
adminQueryProductCategories=this.gql.adminQueryProductCategories();
isInitialized: BehaviorSubject<boolean> = new BehaviorSubject<boolean>(false);
constructor(private gql: GraphqlService) {
this.adminQueryProductCategories.valueChanges.subscribe(({data})=>{
this.categories=data.adminQueryProductCategories;
this.parseCategoryTree(this.categories);
this.isInitialized.next(true);
});
}
теперь это функция, которая извлекает данные из наблюдаемых категорий продуктов:
getCategoryTitleByLabel(label: string): Observable<ProductCategory[]> {
return this.productCategories$.pipe(map((pc: ProductCategory[]) => pc.filter(p => p.category_label === label)));
}
Я не знаю, как реализовать, что наблюдаемое должно выполняться только после того, как инициализированное наблюдаемое истинно.
есть какие-нибудь идеи?
Спасибо
Обновить
в общем, когда пользователь просматривает страницу категории, я хочу проверить, существует ли категория, и если нет, направить его на главную страницу. поэтому при первой загрузке, поскольку запрос graphql не был завершен, он всегда перенаправляется на главную страницу.
это и есть код:
ngOnInit(): void {
this.route.params.subscribe(params => {
this.label = params['category'];
this.pcService.getCategoryTitleByLabel(this.label).subscribe((data)=>{
if (data.length !== 1) {
this.router.navigate(['/']).finally(()=>{
//TODO: popup of some sort ?
});
} else {
** обновление 2 **
это productCategories$
реализовано:
private productCategories = new BehaviorSubject<ProductCategory[]>([]);
get productCategories$(): Observable<ProductCategory[]> {
return this.productCategories;
}
** решение **
большое спасибо за вашу помощь, ребята. решение состояло в том, чтобы просто использовать ReplaySubject
не BehaviorSubject
так, как предлагал @BizzyBob. так
вместо
private productCategories = new BehaviorSubject<ProductCategory[]>([]);
get productCategories$(): Observable<ProductCategory[]> {
return this.productCategories;
}
Я имел:
productCategories$ = new ReplaySubject<ProductCategory[]>();
и это все. проблема решена 🙂
функция получить категорию по метке осталась прежней:
getCategoryTitleByLabel(label: string): Observable<ProductCategory[]> {
return this.productCategories$.pipe(map((pc: ProductCategory[]) => pc.filter(p => p.category_label === label)));
}
Комментарии:
1. Вы можете взглянуть на оператор combineLatest. Каждый раз, когда передаваемые наблюдаемые изменения меняются, вы можете проверить значение и вызвать a
swichMap
, если оно истинно.
Ответ №1:
Вы достигаете того , что ищете, используя
filter
и switchMap
, как это:
getCategoryTitleByLabel(label: string): Observable<ProductCategory[]> {
return this.isInitialized$.pipe(
filter(isInitialized => !!isInitialized),
switchMap(() => this.productCategories$),
map(pc => pc.filter(p => p.category_label === label))
);
}
Но настоящая проблема, по-моему, заключается в этом утверждении:
он возвращает пустой ответ, потому что graphql еще не вернул значение
this.productCategories$
должен выдавать только значимые данные, а не «пустые ответы». Если «пустой» ответ не имеет смысла, что, по-видимому, не так, их, вероятно, следует отфильтровать ранее в потоке. Если бы это было так, вам не нужно было бы представлять это isInitialized$
свойство.
Комментарии:
1. спасибо вам за ваш ответ. функция может быть вызвана с категорией, которой не существует, или которая существует, но данные еще не были инициализированы. в обоих случаях он вернется пустым, как я могу это сделать без isinitalized? обновлено главное сообщение с вызывающим абонентом
2. Где
this.productCategories$
объявлено? Я думаю, что мы должны предотвратить его излучение до получения данных. Тогда нижестоящим потребителям не нужно беспокоиться о возможных условиях гонки, наблюдаемое всегда будет выдавать значимые значения.3. в этом есть смысл. снова обновлен главный пост. как я могу это исправить ?
4. О, я вижу, вы используете BehaviorSubject, для которого требуется значение по умолчанию. Вы могли бы попробовать
ReplaySubject
вместо этого.
Ответ №2:
Попробуйте сделать следующее:
getCategoryTitleByLabel(label: string): Observable<ProductCategory[]> {
return combineLatest({
pc: this.productCategories$,
init: this.isInitialized
}).pipe(
filter(({ init }) => !!init),
map(({ pc }) => pc.filter(p => p.category_label === label))
);
}
Комментарии:
1. getCategoryTitleByLabel нуждается в метке категории в качестве параметра, при необходимости она выполняется динамически. я не совсем понимаю, как я могу использовать его в swtichmap без каких-либо параметров в конструкторе
2. Каков источник метки категории, которую вы хотите передать
getCategoryTitleByLabel
функции?3. Я обновил свой ответ, пожалуйста, проверьте его еще раз.
4. Я думаю, что вы говорите об ответе @BizzyBob, а не о моем. 🙂
Ответ №3:
Мое предложение состоит в том, чтобы подписаться на наблюдаемый объект поведения.
Вы можете получить результат наблюдаемого и выполнить действие:
this.isInitialized.subscribe((res) => {
if (res === true)
{
// do something, maybe set a flag etc.
}
});