Search code examples
angulartypescriptrxjsionic5

Get values from an array received from subject


I have a project in Ionic5 that request some itens to an api. I have a similar code as below:

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

In a page, I have an interval that update the items. Everything works fine if I use this.items = items but the list disappears and reappears every load, probably because the entire value of this.items is changing

So I tried to just insert differents values in array:

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

Unfortunately the subject doesn't await the results from service so just an empty array is rendered in the page. An ugly solution that I found was use a timeout to await the results, but the ms has to change based on length of item list. An observation is that if I log items from item.page.ts it will be print an array with length 0, but with values

enter image description here

I'd like to avoid Promises itself and keep Observables if it's possible.
I cannot return the request directly, because I need to manipulate the data.

Is there any way to await subject.next() to render the items? If not, what logic can I use to show the item list?

SOLVED

The problem was solved using a specific logic of the project. But all answers below will be works in normal environments and has a lot of information in the comments. I really recommend read all of them.


Solution

  • Well first I guess that you have a memory leak in you Item.service.ts file because every time that you call loadItems() you are subscribing and not unsubscribing for this you can use pipe(first()) and also getItems()function creates a Subject on each call.

    Here problem of refreshing is when it replaces the array as you said. although you can check each element of array to see if it's updated or not.

    seems lodash has such a function to check equality of two objects

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

    Also to test it:

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

    And the code:

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

    so in code above you create array inside service to save the data and there is no need for Subject.

    and to get the data you can first connect to array inside service with

    this.items = this.sampleService.getItems;

    and call

    this.sampleService.fetchData() to fetch data from server and because items array is synchronous and also available, you can use it whenever you want.

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