Получить значения из массива, полученного от субъекта - PullRequest
0 голосов
/ 09 июля 2020

У меня есть проект на Ionic5, который запрашивает некоторые элементы API. У меня есть код, аналогичный приведенному ниже:

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);
            })
        })
    }

К сожалению, субъект не ожидает результатов от службы, поэтому на странице отображается пустой массив. Уродливое решение, которое я нашел, заключалось в использовании тайм-аута для ожидания результатов, но мс должно изменяться в зависимости от длины списка элементов. Наблюдение состоит в том, что если я регистрирую элементы из item.page.ts , он будет печатать массив с длиной 0, но со значениями

введите описание изображения здесь

Я бы хотел избежать самих обещаний и сохранить Observables, если это возможно.
Я не могу вернуть запрос напрямую, потому что мне нужно манипулировать данными.

Есть ли любой способ ждать subject.next() для рендеринга элементов? Если нет, то какой logi c я могу использовать для отображения списка элементов?

SOLVED

Проблема была решена с использованием спецификации c logi c проекта. Но все ответы ниже будут работать в обычных средах и содержат много информации в комментариях. Я очень рекомендую прочитать их все.

Ответы [ 3 ]

1 голос
/ 10 июля 2020

Ну, во-первых, я предполагаю, что у вас есть утечка памяти в вашем файле Item.service.ts, потому что каждый раз, когда вы вызываете loadItems (), вы подписываетесь и не отказываетесь от подписки для этого, вы можете использовать pipe(first()), а также getItems() функцию создает тему при каждом вызове.

Здесь проблема обновления заключается в том, что он заменяет массив, как вы сказали. хотя вы можете проверить каждый элемент массива, чтобы узнать, обновлен он или нет.

кажется, что loda sh имеет такую ​​функцию для проверки равенства двух объектов

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() для получения данных с сервера и , поскольку массив элементов синхронный и также доступен, вы можете использовать его в любое время.

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

1 голос
/ 10 июля 2020

Как сказал Эдвард, вам не нужна эта тема.

// 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
  }
1 голос
/ 10 июля 2020

вам не нужна тема в вашем сервисе getItems(). методы HTTP-клиента по умолчанию возвращают наблюдаемые объекты

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

, при этом ваш компонент будет тем, который выполняет подписку.

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

, возвращая наблюдаемое из службы, и перемещая подписку на компонент, когда вы подписываетесь на метод, вы сможете устанавливать элементы, когда HTTP-запрос завершится

...