Как правильно совместить поиск в Firestore с бесконечной прокруткой? - PullRequest
0 голосов
/ 05 мая 2020

После просмотра и чтения ряда руководств у меня есть решение, которое позволяет мне иметь список продуктов на странице и ограничивает отображаемое количество, увеличиваясь по мере прокрутки пользователя, чтобы предотвратить чрезмерное чтение. Это работает, как ожидалось. "поисковый запрос". Опять же, эта функция работает ... но только когда она не сочетается с бесконечной прокруткой.

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

В настоящее время я думаю, что часть моего запроса startAfter работает некорректно, однако я не уверен на 100%. Кажется, что Observable, который я использую для отображения продуктов на моей веб-странице, не сбрасывается при выполнении поискового запроса.

TypeScript:

export class ProductFeedComponent implements OnInit {
  @ViewChild(CdkVirtualScrollViewport,  {static: false})
  viewport: CdkVirtualScrollViewport;

  //detects the end of the product list
  theEnd = false;

  offset = new BehaviorSubject(null);
  infinite: Observable<any[]>;
  searchText = '';

  isHandset$: Observable<boolean> = this.breakpointObserver.observe([Breakpoints.Handset])
  .pipe(
    map(result => result.matches),
    shareReplay()
  );

  constructor(private seo: SeoService, private db: AngularFirestore, private notepad: NotepadsComponent, public dialog: MatDialog, private breakpointObserver: BreakpointObserver) {
    this.searchProducts();
  }

  regularDistribution = 100 / 4 + '%';
  searchQuery: string;

  ngOnInit() {
    this.seo.generateTags({
      title: 'Product Feed',
      description: 'A catalogue filled with products.'
    });
  }

  openDialog(productName: string){
    this.dialog.open(AddToPadComponent,{
      data: {productData: productName}
    });
  }

  nextBatch(e, offset){
    //checks whether this is the end of the list
    if (this.theEnd)
    {
      return;
    }

    const end = this.viewport.getRenderedRange().end;
    const total = this.viewport.getDataLength();

    console.log(this.viewport.getDataLength());

    if (end === total)
    {
      this.offset.next(offset);
    }
  }

  searchProducts(){
    const batchMap = this.offset.pipe(
      throttleTime(500), mergeMap(n => this.getBatch(n)), scan((acc, batch) => {
        return {...acc, ...batch};
      }, {})
    );

    this.infinite = batchMap.pipe(map(v => Object.values(v)));
  }

  trackByIndex(i){
    return i;
  }

  getBatch(lastSeen: string){
    console.log('firing search...')
    console.log(lastSeen)
    const search = (<HTMLInputElement>document.getElementById("productSearch")).value;
    console.log(search);
        return this.db.collection('products', ref => ref.where('keywords', 'array-contains', search).orderBy('productName').startAfter(lastSeen).limit(batchSize))
        .snapshotChanges().pipe(tap(arr => (arr.length ? null : (this.theEnd = true))),map(arr => {
          return arr.reduce((acc, cur) => {
            const id = cur.payload.doc.id;
            console.log(id);
            const data = cur.payload.doc.data();
            console.log(data);
            return {...acc, [id]: {id, data}};
          }, {});
        })
      );
  }
} 

HTML:

<input (keyup)="searchProducts()" id="productSearch" placeholder="search products...">

<ng-container *ngIf="infinite | async as products">
    <cdk-virtual-scroll-viewport itemSize="100" (scrolledIndexChange)="nextBatch($event, (products[products.length - 1].data.productName))">
        <div class="product-row" fxLayout="row wrap" fxLayout.xs="column wrap" fxFlexFill fxLayoutAlign="center center">  
            <div fxFlex.gt-xs="50%" fxFlex.gt-md="30%" *cdkVirtualFor="let p of products; let i = index; trackByIndex">
                    <mat-card class="product-tile">
                        <img class="product-thumbnail" src= "{{ p.data.thumbnail }}" [routerLink]="p.id">
                        <div class="product-brand">
                            {{ p.id }}
                        </div>
                        <div class="product-details">
                            <span id="product-name">{{ p.data.productName }}</span> <span id="product-price">{{ p.data.price }}</span>
                        </div>
                        <div class="add-to-pad-button">
                            <button (click)="openDialog(p.id)" mat-raised-button color="accent">Add to Pad</button>
                            <a href="{{ p.data.link }}" class="mat-raised-button color=accent">Click Me</a>
                        </div>
                    </mat-card>
            </div>
        </div>
    </cdk-virtual-scroll-viewport>
</ng-container>

Я убежден, что упускаю что-то очевидное. Я знаю, что функция работает правильно, когда пользователь выполняет поиск, однако я получаю сообщение об ошибке «Не удается прочитать данные свойства undefined в Object.eval [as handleEvent]», поэтому что-то не так с моим запросом, который не происходит, когда ничего не ищется (например, когда страница изначально загружается).

Рабочий пример: https://www.padder.co.uk/catalogue

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

1 Ответ

0 голосов
/ 30 мая 2020

В соответствии с ссылкой на сайт, которой вы поделились, это мои наблюдения: вы запустили событие поиска на keyUp при поиске терминов, которые не дают никаких результатов, поэтому строка ниже - ошибка

        const data = cur.payload.doc.data();

строка выше дает вам данные об ошибке undefined. Как будто я ищу xyz, но результата нет, вы получаете данные об ошибке undefined, так как она не существует, но если вы ищете синий, вы не получите эту ошибку, как вы есть результат для этого (когда возвращается ответ на синий запрос).

Вы должны проверить, имеет ли следующий объект свойство данных или нет.

то, что вы можете сделать, это

      const id = cur.payload.doc.id;

      console.log(id);

      let data = ''; // what ever value you need , or a empty object 
     // check for id as, if you have result then id value will be there and then use data 
    if(id){ 
             data = cur.payload.doc.data();
         }

-----
 // or 
if(cur.payload.doc && cur.payload.doc.data){
data = cur.payload.doc.data();
}

, поэтому вы не получите ошибку не можете прочитать данные свойства undefined

Также я порекомендую вам добавить / улучшить немного функциональности,

1) Для поиска - используйте время устранения дребезга, уникальное значение, начните поиск после 2 набранных уставов для стороны производительности. Прямо сейчас, когда вы нажимаете клавишу, она заполняет панель n / w запросами.

2) В шаблоне - {{ p?.data?.productName }} - используйте это как необязательный параметр, чтобы избежать ошибки, если вы не уверены, что свойство есть или нет

...