使用 RxJS Subject 延迟加载数据。最后一批未显示,主题永远不会完成

Lazy Loading data with a RxJS Subject. Last batch is not displayed, the subject never completes

我使用假后端延迟加载数据。后端 returns 一个数组。因为我想延迟加载数据,所以我每 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 提供一个初始值,有什么办法可以避免这种情况吗?

提前致谢!

这似乎是一种非常复杂的做事方式

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

这一行 returns 一个新数组,其长度与每个槽中包含未定义的数据相同,如果你想从数组创建一个可观察对象,那么你去

this.categories$ = from(data);

没有初始值的 BehaviorSubject 是一个 Subject。

除此之外,您已经将数据放在一个数组中,无需从中创建新的可观察对象。

有一个 BehaviorSubject,里面有一个数字,每次按键都会增加数字,并使用 combineLatest 从数组中获取你想要的元素数量。

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

从 1 开始页面并在每次单击按钮时递增页面,您真正需要做的就是这些。