【问题标题】:Convert Object to Array then display one element every few seconds将对象转换为数组,然后每隔几秒显示一个元素
【发布时间】:2021-04-03 02:57:54
【问题描述】:

我像这样在我的服务中调用 API

getItems(): Observable<{}> {
    return this.http.get(<path>)
    .pipe(
      pluck('items'),
    );
  }

以这种形状返回数据:

items: Object { key: value, key: value, key: value ...}

我在我的组件中订阅了这个

items$!: any;
constructor(private readonly service: Service) { }

  ngOnInit(): void {
    this.service.getItems().subscribe(items => this.items$ = items);
  }

并像这样在我的模板中显示它们

<div *ngFor="let item of items$ | keyvalue">
    <mat-card>
        <mat-card-header>
          {{item.key}}
        </mat-card-header>
        <mat-card-content>
          {{item.value}}
        </mat-card-content>
    </mat-card>
</div>

但是,我真正想要的是每隔几秒左右一次循环显示这些项目,而不是一次显示所有项目。我还想只进行一次 http 调用来完成此操作。我环顾四周,发现了解决方案的一些细节,但是,它通常涉及至少将对象转换为数组,然后可能使用某种主题来观察订阅的原始 observable?有什么方法可以做到这一点?谢谢。

【问题讨论】:

  • 您是否希望它们全部同时显示,但速度很慢(就像一个缓慢增长的数组)。还是要先显示一个,再显示下一个,再显示下一个(如幻灯片放映)?
  • 像幻灯片一样

标签: angular typescript rxjs angular-httpclient angular-observable


【解决方案1】:

一种解决方案是将 API 中的数据转换为具有以下结构的数组:

const apiData = [{ a: 1 }, { b: 2 }, { c: 3 }, { d: 4 }, { e: 5 }, { f: 6 }];

接下来,您可以使用以下代码,该代码使用 bufferCountconcatMapdelay 运算符

data = [];

from(apiData)
  .pipe(
    bufferCount(1),
    concatMap(objs => of(objs).pipe(delay(3000)))
  )
  .subscribe(e => {
    this.data.push(e);
  });

在模板上

<div *ngFor="let item of data">
    {{item | json}}    <-- tweak around with it to get exact details you need -->
</div>

【讨论】:

  • 谢谢,这可能会奏效。但是,在这种情况下如何从对象转换为数组?我已经尝试过here 提到的内容,但无法完全使用 getItems() 方法。
  • 您可以使用Object.entries 做到这一点。一个例子,var obj = { a: 1, b: 2, c: 3, d: 4 }; let test = []; for (const [key, value] of Object.entries(obj)) { test.push({ [key]: value }) }
  • 我真的在为这种转换而苦苦挣扎。我尝试了this.service.getItems().subscribe(items =&gt; this.items = this.objectToArray(items));,其中 objectToArray 是您评论中的方法。 this.items 之后未定义。
  • 无论如何都想出了转换。
【解决方案2】:

创建一个每 5 秒发出一个值并与所有订阅者共享相同值的流。


item$: Observable<Item>;

ngOnInit(): void { 
  this.item$ = this.service.getItems().pipe(
    switchMap(items => zip(
      from(items),
      timer(0,5000)
    )),
    map(([x,_]) => x),
    share()
  );
}

订阅流。异步管道是使用 angular 实现此目的的最佳方式

<div>
    <mat-card>
        <mat-card-header>
          {{(item$ | async).key}}
        </mat-card-header>
        <mat-card-content>
          {{(item$ | async).value}}
        </mat-card-content>
    </mat-card>
</div>

更新 - 使用自定义运算符

function intervalArray<T>(milliseconds = 1000): OperatorFunction<T[], T> {

  return s => s.pipe(
    concatMap((a: T[]) => zip(
      from(a),
      timer(0, milliseconds)
    ).pipe(
      map(([x,_]) => x)
    ))
  );
  
}

ngOnInit(): void { 
  this.item$ = this.service.getItems().pipe(
    intervalArray(5000),
    share()
  );
}

【讨论】:

  • 这看起来也不错,但它导致我在带有 .key 和 .value 的模板中出现“对象可能为空”错误
  • 试试(item$ | async)?.key:相关问题:stackoverflow.com/questions/61420325/…
  • 这几乎可以正常工作,除非您第一次导航到页面时出于某种原因,第一个值不起作用。但此后每 5 秒它就会做它应该做的事情。我也收到了其中几个错误ERROR TypeError: _angular_core__WEBPACK_IMPORTED_MODULE_0__.\u0275\u0275pipeBind1(...) is null
  • 我不喜欢角度,不是我的事。似乎 ngOnInit 等待的时间不够长。一个简单的答案是尝试timer(250,5000) 之类的东西,但那是代码异味。真正的答案是找出哪个 Angular 的回调最适合完全加载的 HTML,然后确定 shareReplay(1) 而不是 share() 或者使用 startWith(defaultValue)
  • 谢谢。使用 shareReplay(1) 也会显示第一个值,之后它也可以完美运行。但是,由于某种原因,如果您刷新它,那么它只会偶尔起作用。当它不起作用时,我会收到 ERROR TypeError: can't convert undefined to object 的错误,它来自我的 objectToArray 方法,我将项目传递给该开关映射(因为该点的项目是我必须转换为数组的对象)。这显然不是因为它没有被破坏——异步管道负责取消订阅 observables 必须是别的东西
【解决方案3】:

谢谢尼古拉斯先生。你一直很有帮助。我将 Mrk 的答案标记为答案,因为基本上我使用了他们的代码。以防万一有人好奇是什么解决了我在 cmets 中提到的几件事,并想在上下文中查看整个解决方案,这就是我所做的全部

//in the service
getItems(): Observable<{}> {
    return this.http.get(<URL>)
    .pipe(
      pluck('items'),
    );
  }

//in the template, this change stopped the initial null issue I was having
<div *ngIf="item$ | async as state">
  <mat-card>
      <mat-card-header>
        {{state.currencyPair}}
      </mat-card-header>
      <mat-card-content>
        {{state.exchangeRate}}
      </mat-card-content>
  </mat-card>
</div>

//in the component
  item$!: Observable<Item>;
  constructor(private readonly service: Service) { }

  ngOnInit(): void {
    this.item$ = this.service.getItems().pipe(
      switchMap(items => zip(
        from(this.objectToArray(items)),
        timer(0,5000)
      )),
      map(([x,_]) => x),
      shareReplay(1)
    );
  }

  objectToArray(object: {}) {
    let items: Item[] = [];
    for (const [key, value] of Object.entries(object)) { 
      items.push({ key : key, value : value })
    }
    return items;
  }

//item.ts
export interface Item {
    key: string;
    value: any;
}

现在刷新 objectToArray 后得到未定义对象的问题显然是 API 的问题,因为我的免费计划不支持 https 加密哈哈。我认为我通过将 s 从 URL 中取出来解决了这个问题,但 耸了耸肩

Nicholas,如果您愿意分享,我真的很想看看您的代码在上下文中是如何工作的。无论如何,我需要深入了解 Angular 库并了解更多信息。谢谢!

【讨论】:

    猜你喜欢
    • 1970-01-01
    • 2021-09-11
    • 1970-01-01
    • 2014-06-15
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    相关资源
    最近更新 更多