запустите наблюдаемое только после того, как значение другого наблюдаемого будет истинным, а не ложным

#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.
    }
});