📜  异步管道 ngfor - Html (1)

📅  最后修改于: 2023-12-03 15:09:48.624000             🧑  作者: Mango

异步管道 ngfor - Html

在 Angular 应用程序中,ngFor 是一个很常用的指令,用于在 HTML 模板中迭代数据。通常,ngFor 可以在同步情况下正常工作,但在异步情况下会遇到问题,例如从 API 加载数据等。为了解决这个问题,Angular 提供了一个异步管道,即异步管道 ngFor。

如何使用异步管道 ngFor
  1. 引入 AsyncPipe (name 必须为 common,这样才可以在多个 module 中使用)
import { CommonModule } from '@angular/common';
  1. 使用自定义的管道 NgForAsyncDirective(自定义名称可以设置)。该指令的核心是调用内置的 ngFor 指令和 AsyncPipe。

ngForAsync.directive.ts:

import { Directive, Input, OnChanges, OnDestroy, SimpleChange, SimpleChanges, TemplateRef, ViewContainerRef } from '@angular/core';
import { AsyncPipe } from '@angular/common';
import { Observable, Subscription } from 'rxjs';

@Directive({
  selector: '[ngForAsync]'
})
export class NgForAsyncDirective<T> implements OnChanges, OnDestroy {
  @Input() ngForAsync: Observable<T[]> | undefined;
  @Input() ngForAsyncTrackBy: ((index: number, item: T) => any);
  @Input() ngForAsyncTemplate: TemplateRef<any>;

  private items: T[] | undefined;
  private subscription: Subscription | undefined;

  constructor(private viewContainer: ViewContainerRef, private templateRef: TemplateRef<any>, private asyncPipe: AsyncPipe) {
    this.viewContainer.clear();
  }

  private updateView(items: T[]) {
    this.viewContainer.clear();
    items.forEach((item, index) => {
      const childView = this.viewContainer.createEmbeddedView(this.ngForAsyncTemplate, {
        $implicit: item,
        index
      });
      childView.detectChanges();
    });
  }

  private updateSubcription() {
    if (this.subscription && !this.subscription.closed) {
      this.subscription.unsubscribe();
    }

    this.subscription = this.ngForAsync?.subscribe(items => {
      this.items = items;
      this.updateView(items);
    });
  }

  public ngOnChanges(changes: SimpleChanges) {
    const ngForAsyncChange: SimpleChange = changes['ngForAsync'];
    if (ngForAsyncChange) {
      this.updateSubcription();
    }
  }

  public ngOnDestroy() {
    if (this.subscription && !this.subscription.closed) {
      this.subscription.unsubscribe();
    }
  }
}
  1. 在组件中使用异步管道 ngFor。与正常的 ngFor 相似,只需传递一个 Observable,然后在模板中使用自定义的指令。

app.component.ts:

import { Component } from '@angular/core';
import { Observable, of } from 'rxjs';

@Component({
  selector: 'app-root',
  template: `
    <ul>
      <li *ngForAsync="let item of items$; trackBy: trackByFn; template: itemTemplate">{{ item.name }}</li>
    </ul>
    <ng-template #itemTemplate let-item let-i="index">
      {{ i + 1 }} - {{ item.id }} - {{ item.name }}
    </ng-template>
  `
})
export class AppComponent {
  items$: Observable<any[]> = of(
    [
      {
        id: 1,
        name: 'Item 1'
      },
      {
        id: 2,
        name: 'Item 2'
      }
    ]
  );

  trackByFn(index: number, item: any) {
    return item.id;
  }
}
示例说明

在上面的示例中,AppComponent 定义了一个 items$ Observable。这个 Observable 反映了从 API 加载数据的异步情况。items$ 传递到模板中的 *ngForAsync 中,传递另外两个参数,即 trackByFn 和 itemTemplate。注意,这里使用自定义的指令 *ngForAsync 而不是原生的 *ngFor。

最后,为 itemTemplate 定义了一个 ng-template。这个 ng-template 定义了要显示的内容。通过内置的 $implicit 和 index 变量,我们可以引用 item 和 index。

注意:在 *ngForAsync 中,我们必须设置跟踪函数 trackByFn。这个函数是用来跟踪每个列表项的,以避免不必要的重新渲染。我们可以选择某个唯一的值来跟踪每个列表项。在上面的示例中,我们使用了每个项的 id。

参考
  1. Asynchronous ngFor in Angular
  2. Angular - AsyncPipe
  3. Angular - Directive