-1

I am using REST services in Angular 8 app and using behaviourSubject to populate the received values in component. It works fine but during developing I noticed that next(someValue) works not as I expected.

The emitted value (with next) at the point in code is empty. The value changes only later (in some milisecs when REST completes). Does it get assigned to the value and waits until it changes? Please, look in the code for this place:

    console.log(this.tempImages.length);
    this.sImages.next(this.tempImages);

I will appreciate a good explanation as I searched throughout and could not find the answer.

import { Injectable } from '@angular/core';
import { HttpClient } from '@angular/common/http';
import { Observable, BehaviorSubject } from 'rxjs';
import { map } from 'rxjs/operators';

import * as env from '@environments/environment';

class SearchResult {
  total: number;
  objectIDs: number[];
}

@Injectable({
  providedIn: 'root'
})
export class RestService {

  tempImages = [];
  searchIDs = {
    total: 0,
    objectIDs: [0]
  };
  sImages: BehaviorSubject<any[]> = new BehaviorSubject([]);
  sMessage: BehaviorSubject<string> = new BehaviorSubject('');

  constructor(private http: HttpClient) {
    this.sImages = new BehaviorSubject<any[]>(JSON.parse(localStorage.getItem('restImages')));
    this.sMessage = new BehaviorSubject<string>(localStorage.getItem('restMessage'));
    this.searchIDs = JSON.parse(localStorage.getItem('searchIDs'));
  }

  getDeps(): Observable<any> {
    return this.http.get(`${env.departmentUrl}`);
  }

  getIds(theTerm: string, theDep?: number) {
    this.tempImages = [];
    this.sImages.next([]);
    this.sMessage.next('');
    this.searchIDs = {
      total: 0,
      objectIDs: [0]
    };
    const searchUrl = theDep !== undefined ? `${env.searchTermAndDepUrl(theTerm, theDep)}` : `${env.searchTermUrl(theTerm)}`;
    return this.http
      .get<SearchResult>(searchUrl)
      .pipe(map(result => {
        this.searchIDs = result;
        localStorage.setItem('searchIDs', JSON.stringify(this.searchIDs));
        if (this.searchIDs.total === 0) {
          this.sMessage.next('No results');
          localStorage.setItem('restMessage', this.sMessage.value);
          return;
        }
        if (this.searchIDs.objectIDs.length > 20) {
          this.sMessage.next('Your search returned more than 20 results. Here are the top 20');
          localStorage.setItem('restMessage', this.sMessage.value);
        }
        for (let i = 0; i < this.searchIDs.objectIDs.length && i < 20; i++) {
          this.getImage(this.searchIDs.objectIDs[i]).subscribe();
        }

        // I can't get this place
        // this.tempImages.length is 0 at this point. If is set timeout it gets filled of course.
        // why sending next(..) at this point works? If the array is still empty?
        
        console.log(this.tempImages.length);
        this.sImages.next(this.tempImages);

      }));
  }

  getImage(id: number): Observable<any> {
    return this.http.get(`${env.searchArtItemUrl(id)}`).pipe(map(image => {
      this.tempImages.push(image);
      if (this.searchIDs.objectIDs.length === this.tempImages.length || this.tempImages.length === 20) {

        localStorage.setItem('restImages', JSON.stringify(this.tempImages));
      }
    }));

  }
}
MMarch
  • 65
  • 2
  • 5

2 Answers2

0

getImage is asynchronous so you need to wait for all the function on the loop to complete before you send the tempImags to the BehaviorSubject, you should use rxjs forkJoin to wait for all the values see

madalinivascu
  • 32,064
  • 4
  • 39
  • 55
  • but it works without forkJoin. I expected it not to work. That's why I got a question - why it's working? – MMarch May 31 '21 at 09:16
  • if you subscribe to `sImages` you will see that the values received are either `[]` or a partial list of `tempImages`, the next function accepts any type of data – madalinivascu May 31 '21 at 10:25
0

The use of a timeout will not work in all situations. What if the loop containing the getImage() call takes an indeterminate time to complete?

So you can try the following to delegate the logging and sImages array update to another observable which waits until the looping completes:

bImagesRetrieved: BehaviorSubject<bool> = new BehaviorSubject(false);

...

ngOnInit()
{
  this.bImagesRetrieved.subscribe(res => 
  {
    if (res)
    { 
      console.log(this.tempImages.length);
      this.sImages.next(this.tempImages);     
    }
  });
}

getIds(..)
{
   ...

   for (let i = 0; i < this.searchIDs.objectIDs.length && i < 20; i++) {
      this.getImage(this.searchIDs.objectIDs[i]).subscribe();  
   }

   bImagesRetrieved.next(true);

   ...
}
Andrew Halil
  • 1,195
  • 15
  • 15
  • 21