从主题接收的数组中获取值

Get values from an array received from subject

我在 Ionic5 中有一个项目,它向 api 请求一些 itens。 我有一个类似的代码如下:

Item.service.ts

getItens(): Observable<Item[]> {
    const subject = new Subject<Item[]>();
    this.http.get<Response>('host').subscribe(data => {
        // Some cool things
        subject.next(data.items)
    }, error => {
        subject.error(error.message)
    });

    return subject.asObservable();
}

items.page.ts

items: Item[] = [];

    loadItems() {
        this.itemService.getItems().subscribe(items => this.items = items);
    }

在一个页面中,我有一个更新项目的时间间隔。如果我使用 this.items = items 一切正常,但列表会在每次加载时消失并重新出现,可能是因为 this.items 的整个值正在改变

所以我试着在数组中插入不同的值:

items: Item[] = [];

    loadItems() {
        this.itemService.getItems().subscribe(items => {
            items.forEach(item => {
                const value = this.items.find(i => i.id == item.id);
                if(!value) this.items.unshift(item);
            })
        })
    }

不幸的是,主题不等待服务的结果,因此页面中只呈现一个空数组。我发现的一个丑陋的解决方案是使用超时来等待结果,但 ms 必须根据项目列表的长度进行更改。 一个观察是,如果我记录来自 item.page.ts 的项目,它将打印一个长度为 0,但值

的数组

如果可能的话,我想避免 Promises 本身并保留 Observables。
我不能直接return请求,因为我需要操作数据。

有什么方法可以等待 subject.next() 来渲染项目吗?如果不是,我可以使用什么逻辑来显示项目列表?

已解决

使用项目的特定逻辑解决了问题。但是下面的所有答案都适用于正常环境,并且在评论中有很多信息。我真的推荐阅读所有这些。

您的 getItems() 服务中不需要主题。默认情况下,http 客户端方法 return observables

getItems(): Observable<Item[]> {
  return this.http.get<Response>('host');
}

如此一来,您的组件将成为进行订阅的组件。

loadItems() {
    this.itemService.getItems().subscribe(items => this.items = items);
}

通过 return 从服务中获取可观察对象,并将订阅移动到组件,当您订阅该方法时,您将能够在 HTTP 请求完成时设置项目

首先,我猜你的 Item.service.ts 文件中存在内存泄漏,因为每次你调用 loadItems() 时你都在订阅而不是取消订阅,你可以使用 pipe(first()) getItems()函数在每次调用时创建一个主题。

这里刷新的问题就是你说的替换数组的时候。尽管您可以检查数组的每个元素以查看它是否已更新。

lodash 好像有检查两个对象是否相等的函数

https://www.samanthaming.com/tidbits/33-how-to-compare-2-objects/

也来测试一下:

https://stackblitz.com/edit/angular-lodash-tdggzs?file=app%2Fapp.component.ts

代码:

import * as _ from 'lodash';

export class SampleService {
  items: Item[] = [];

  constructor(private http: HttpClient) {}

  get getItems(): Item[] {
    return this.items;
  }

  fetchData(): void {
    this.http
      .get<Item[]>('host')
      .pipe(first())
      .subscribe((newArray) => {
        this.updateMyArray(newArray);
      });
  }

  updateMyArray(newArray: Item[]) {
    newArray.forEach((item) => {
      const itemInLocalArray = this.items.find((x) => x.id === item.id);
      if (itemInLocalArray) {
        const isItSame = _.isEqual(itemInLocalArray, item);
        if (!isItSame) {
          this.items[this.items.indexOf(itemInLocalArray)] = item;
        }
      } else {
        this.items.push(item);
      }
    });
  }

}

所以在上面的代码中,您在服务中创建数组来保存数据,并且不需要 Subject。

要获取数据,您可以先使用

连接到服务中的数组

this.items = this.sampleService.getItems;

并致电

this.sampleService.fetchData() 从服务器获取数据,因为 items 数组是同步的并且也是可用的,所以你可以随时使用它。

 // Inside Component
  items: Item[] = [];
  constructor(private sampleService: SampleService) {
    this.items = this.sampleService.getItems;
  }
  
  fetchData(){
    this.sampleService.fetchData();
  }

正如爱德华所说,你不需要那个主题。

// Move your "cool things" and all the other stuff in that subscription into a pipe
getItens(): Observable<Item[]> {

retrun this.http.get<Response>('host').pipe(
       map(data => {
        // Some cool things
        return data.items
      }),
      catchError(error => {
       // if you need to only pass error message you can do it here but you could probably skip this. Make sure you re-thow the error though, so other subscribers see it as an error and not just a valid result.
       return throwError(error.message);
      })
);
}

关于重新呈现整个列表的问题,*ngFor 有一个 trackBy 参数可以帮助解决这个问题。您提供一个函数,它可以帮助 angular 了解数组中的哪些项目发生了变化,并且只重新呈现这些项目。

angular 文档具有 trackBy 语法 https://angular.io/api/common/NgForOf

会是这样的 模板:

<my-component *ngFor="let item of items; trackBy: trackByFn> </my-component>

组件

trackByFn(index, item) {
    return item.id // or whatever the key property is
  }