Observable: получение уникальных значений массива из документов коллекции

#angular #firebase #ionic-framework #google-cloud-firestore

#angular #firebase #ionic-framework #google-облако-firestore

Вопрос:

Каков наилучший способ создать список уникальных значений из массивов Firestore?

Каждый документ в коллекции может иметь любое количество категорий, которые записаны в массиве документов «категории».

Я пытаюсь получить все уникальные категории значений массива для заполнения параметров фильтрации, поэтому мне нужно быть уверенным, что для этой категории есть отель. Я не могу использовать только список всех категорий из другой коллекции.

Angular 8, Firestore, angularfire 2, Ionic 4

Observable:

 this.hotels = afs.collection('hotels', ref => ref.where('city_id', '==', cityId).orderBy('reviews_number', 'desc')).valueChanges();
  

Компонентный html:

 <li *ngFor="let hotel of hotels | async">
{{ hotel.categories }}
</li>
  

Результат:

 wifi
wifi
wifi,pool,center
pool
  

И я не могу понять, как лучше всего получать уникальные значения из массивов, чтобы иметь возможность заполнять выпадающий список всеми вариантами уникальных значений.

 wifi,pool,center
  

Ответ №1:

Чтобы все было чище, я бы предложил использовать отдельную переменную для размещения ваших выпадающих значений, подписаться на значения Firestore и затем извлекать уникальные значения

TYPESCRIPT:

 class MyComponent {
  dropDownOptions: Array<any> = [];
  someMethod() { // only called once in the component
    this.hotels = afs.collection('hotels', ref => ref.where('city_id', '==', cityId)
      .orderBy('reviews_number', 'desc'))
      .valueChanges();
    this.hotels.subscribe((hotels) => {
      const options: Array<string> = hotels.reduce((prevValue, hotel) => {
        return [...prevValue, ...hotel.categories];
      }, []);
      
      this.dropDownOptions = options.filter(this.onlyUnique); // get unique values
    })
  }

  onlyUnique(value, index, self) { 
    return self.indexOf(value) === index;
  }

}
  

HTML:

 <select>
  <option *ngFor="let option of dropDownOptions" value="{{option}}">
    {{ option }}
  </option>
</select>
  

Обновить:

В HTML была опечатка. Т.е. dropdownOptions должно было быть dropDownOptions . Исправлено. Кроме того, я привел пример Stackblitz, который демонстрирует поведение.

Комментарии:

1. Спасибо за помощь, окончательный код должен выглядеть следующим образом: pastebin.com/B2nv5FAh но dropdownOptions пуст

2. в HTML при выполнении {{ dropDownOptions }} я получаю уникальные значения! Wi-Fi, пул, центр — это работает, просто * ngFor не возвращает значения

3. @TedAce пожалуйста, проверьте мой ответ (раздел ОБНОВЛЕНИЯ), я добавил пример stackblitz, который демонстрирует решение. Надеюсь, это поможет.

4. Работает, как я не проверял ошибку типа :))) Может быть, вы знаете, как исключить пустые значения? Поскольку теперь также включено пустое значение (у отеля нет какой-либо категории). Еще раз спасибо

5. Вы можете изменить return [...prevValue, ...hotel.categories]; в коде на return [...prevValue, ...hotel.categories.filter(category => category !== "")]; , чтобы исключить пустые строки в качестве категорий. Также, пожалуйста, поддержите ответ и комментарии, если они вам помогли 🙂 Спасибо и удачного кодирования!

Ответ №2:

Вы могли бы использовать mergeMap() от RxJS для выделения отдельных объектов hotel, distinct() для фильтрации дубликатов на основе идентификатора и архивирования его обратно в массив с
toArray()

 this.hotels = afs.collection('hotels', ref => ref.where('city_id', '==', cityId)
  .orderBy('reviews_number', 'desc'))
  .valueChanges()
  .pipe(mergeMap(hotels => hotels), distinct(hotel => hotel.id), toArray());
  

Я не работал с Firebase, но, если бы мне пришлось догадываться, valueChanges () — это бесконечный поток. В этом случае toArray () не будет работать, и вам нужно будет сканировать() значения и помещать в массив.

 this.hotels = ...valueChanges().pipe(
  mergeMap(hotels => hotels), 
  distinct(hotel => hotel.id), 
  scan((acc, val) => acc.concat(...val), []));