Отложенная загрузка данных с темой RxJS. Последний пакет не отображается, объект никогда не завершается

#javascript #angular #ecmascript-6 #rxjs #behaviorsubject

#javascript #angular #ecmascript-6 #rxjs #behaviorsubject

Вопрос:

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

multiselect.service.ts

 import { Injectable } from '@angular/core';
import { HttpClient, HttpHeaders, HttpResponse } from '@angular/common/http';
import { BehaviorSubject } from 'rxjs';

const httpOptions = {
  headers: new HttpHeaders({ 'Content-Type': 'application/json' })
};

export interface ProductCategory {
  id: number;
  name: string;
}

@Injectable({
  providedIn: 'root'
})

export class MultiSelectService {

  constructor(private http: HttpClient) { }

  private readonly categorySubject = new BehaviorSubject(undefined);
  readonly categories$ = this.categorySubject.asObservable();

  // URL to web api - mocked. Categories is the object present in the mock server file
  private categoriesUrl = 'api/categories';

  /** GET heroes from the server */
  getCategories(): void {
    this.http.get<Array<ProductCategory>>(this.categoriesUrl, httpOptions)
    .subscribe( data => {
      data.map( category => this.categorySubject.next(category));
      this.categorySubject.subscribe( xyz => console.log(xyz));
    });
  }
}
  

multiselect.component.ts

 import { Component, AfterViewInit } from '@angular/core';
import { zip, Observable, fromEvent } from 'rxjs';
import { MultiSelectService, ProductCategory } from './multiselect.service';
import { bufferCount, startWith, map, scan } from 'rxjs/operators';

@Component({
  selector: 'multiselect',
  templateUrl: './multiselect.component.html',
  styleUrls: ['./multiselect.component.scss']
})
export class MultiselectComponent implements AfterViewInit {

  SLICE_SIZE = 100;

  constructor(private data: MultiSelectService) { }

  ngAfterViewInit() {
    const loadMore$ = fromEvent(document.getElementsByTagName('button')[0], 'click');
    this.data.getCategories(); // loads the data

    zip(
      this.data.categories$.pipe(bufferCount(this.SLICE_SIZE)),
      loadMore$.pipe(startWith(0)),
    ).pipe(
      map(results => results[0]),
      scan((acc, chunk) => [...acc, ...chunk], []),
    ).subscribe({
      next: v => console.log(v),
      complete: () => console.log('complete'),
    });
  }

}
  

multiselect.component.html

 <button>Fetch more results</button>
  

1) Количество элементов равно 429, и я показываю их партиями по 100 элементов. После четырех щелчков (4 x 100) последние 29 никогда не отображаются. Чего мне не хватает для отображения последних 29 элементов? Я смог проверить, что объект выдал все значения.

2) Есть ли какой-либо лучший способ достижения той же функциональности, которую я разрабатываю здесь? Я сопоставляю массив (вместо этого это может быть цикл forEach) из-за того, что после запроса данных я получу только один элемент (массив) со всеми элементами (429). Это не позволит мне буферизировать элементы, если я хочу выполнить отложенную загрузку.

3) Я должен предоставить начальное значение для BehaviorSubject, есть ли какой-либо способ избежать этого?

Заранее спасибо!

Ответ №1:

Это кажется очень запутанным способом выполнения действий

 data.map( category => this.categorySubject.next(category));
  

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

 this.categories$ = from(data);
  

Объект BehaviorSubject без начального значения является объектом.

Помимо всего этого, у вас уже есть ваши данные в массиве, нет необходимости создавать из него новый observable.

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

 combineLatest(categories$, page$).pipe(map(([categories, page]) => categories.filter((item, index) => index < page * pageSize)))
  

Это все, что вам действительно нужно сделать, когда вы начинаете страницу с 1 и увеличиваете ее при каждом нажатии кнопки.