如何在 Angular 9 中将分页与 Observables 和 AsyncPipe 结合起来?

How to combine a pagination with Observables and the AsyncPipe in Angular 9?

有一份来自 API 的产品列表。产品已分页,用户可以切换到另一页。简化的模板如下所示:

<ul>
  <li *ngFor="let product of products$ | async">{{ product.name }}</li>
</ul>

<button type="button" (click)="gotoPage(1)">1</button>
<button type="button" (click)="gotoPage(2)">2</button>

该组件看起来像这样:

export class ProductsComponent implements OnInit {
  products$: Observable<Product[]>;

  constructor(
    private service: ProductService
  ) { }

  ngOnInit() {
    this.products$ = this.service.getAll({page: 1});
  }

  gotoPage(page: number): void {
    this.products$ = this.service.getAll({page: page});
  }
}

我的问题是: 这是更新 Obersavble 的正确方法吗?或者这会产生内存泄漏吗?

请注意:URL 不会更改,并且组件不应在分页更改时重新加载。

您始终可以通过 takeUntil + ngOnDestroy 模式防止内存泄漏。

例如,

声明一个新变量private onDestroy$: Subject<void> = new Subject<void>();

this.service.getAll({page: page})
.pipe(takeUntil(this.onDestroy$))
.subscribe(... do the necessary ...);

稍后,在 onDestroy() 生命周期钩子中,您可以实现以下内容:

 public ngOnDestroy(): void {
   this.onDestroy$.next();
   this.onDestroy$.complete() 
 }

我们实际上做的是声明一个新的可观察对象;然后,通过使用带有 takeUntil 的管道方法,当 onDestroy$ 中出现任何值时,我们通知编译器我们想要取消订阅可观察对象,然后,通过使用带有 takeUntil 的管道方法,我们通知编译器,当任何值出现时,我们想要取消订阅可观察对象在 onDestroy$ 中,从而防止内存泄漏。

你甚至没有订阅你的 observable,所以我不认为这里可能存在内存泄漏,你只是获取一些数据,异步管道为你处理 'transformation'。

以防万一,当你订阅一个可观察对象时,你需要添加几行代码来正确取消订阅并防止内存泄漏:

ngUnsubscribe = new Subject();
myObservable: Observable<any>;

ngOnInit(){
  this.myObservable.pipe(takeUntil(ngUnsubscribe))
  .subscribe(...)
}

ngOnDestroy() {
  this.ngUnsubscribe.next();
  this.ngUnsubscribe.complete();
}

一个主题,可让您在每个具有 takeUntil 的订阅上触发正确的取消订阅。

.next.complete 是必要的,因为 .unsubscribe 没有按预期工作(在与 NGRX 一起工作时注意到它并发现了一些讨论它的 Whosebug 线程)。

查看Async管道的source,可以看到里面的transform()函数:

if (obj !== this._obj) {
  this._dispose();
  return this.transform(obj as any);
}

取消订阅以前的 Observable 如果有一个,如果它是一个新对象。所以你可以安全地使用它。

众所周知,每页的项目数也随页面而来。

我更喜欢将标准更改为行为主题并将两个可观察值与 mergeMap

结合起来

class ProductService {
  constructor() {
    this.defaultCriteria = {
      page: 0,
      pageSize: 5
    }
  }

  getAll(criteria) {
    criteria = {
      ...this.defaultCriteria,
      ...criteria
    }

    return rxjs.of(Array(criteria.pageSize).fill(0).map((pr, index) => {
      const num = index + criteria.pageSize * criteria.page + 1;
      return {
        id: num,
        name: `Product ${num}`
      }
    }))
  }

}

class ProductsComponent {
  constructor(service) {
    this.service = service;
    this.page$ = new rxjs.BehaviorSubject(0);
    this.products$ = null;
  }

  ngOnInit() {
    this.products$ = this.page$.asObservable()
      .pipe(rxjs.operators.mergeMap(page => this.service.getAll({
        page
      })))
  }

  gotoPage(page) {
    this.page$.next(page);
  }
}

const service = new ProductService();
const component = new ProductsComponent(service);
component.ngOnInit();
component.products$.subscribe(products => console.log(products));
component.gotoPage(1);
component.gotoPage(2);
<script src="https://cdnjs.cloudflare.com/ajax/libs/rxjs/6.5.5/rxjs.umd.js"></script>