2

I have an item list in a service, and a getter that wraps that list in an observable.
The observable is consumed in a components view with async pipe, and works as expected. When the list is updated, the view is updated.

I also have a different component that needs to get an observable of a specific item from that list, based on an id.
The thing is that the item with that id might not be in the list yet, when it is requested. How can I go about achieving this?

Some examples of what I tried:

export class ItemsService {
  private itemList: Item[] = [];

  constructor() {
    // get the list from the backend    
  }

  // This is fine
  getItemList(): Observable<Item[]> {
    return of(this.itemList);
  }

  // This I assume does not work, because the pipe just applies map 
  //on whatever is now in the observable list
  getItem(id: string): Observable<Item> {
    return this.getItemList().pipe(map(items => items.find(item => item.id === id)));
  }

  // This I assume does not work as the local item is not yet set when I wrap it in an observable
  //to return it, and when it eventually gets set by the callback, it's already out of scope.
  // The weird thing to me is that the callback is only called at the beginning, when the list is empty,
  //and not anymore when the list gets populated
  getItem(id: string): Observable<Item> {
    let item: Item;
    this.getItemList().subscribe(items => {
      console.log('callback called');
      item = items.find(item => item.id === id);
    });
    return of(item);
  }
}

1
  • Do we need to have a subscribe() code block in an Angular service? Commented Nov 12, 2019 at 19:48

3 Answers 3

4

One possible approach to get a notification when a given element is available is to use a Subject.

Add this into your Service:

private subject = new Subject<Item>();

Once you receive your data, loop through it and feed it to the subject.

  this.itemList.forEach((item: Item) => {
    this.subject.next(item);
  });

The third part is to get the notification.

  getItem(id: string): Observable<Item> {
    return this.subject
      .pipe(
        filter((item: Item) => item.id === id)
      )
  }

Use it from your component something like this:

  ngOnInit() {
    const waitForElementWithId = 'b1';

    this.itemsService.getItem(waitForElementWithId).subscribe(x => {
      console.log(`${waitForElementWithId} is here`);
    });
  }

Working Stackblitz.

Sign up to request clarification or add additional context in comments.

Comments

3

Try this:

getItem(id: string): Observable<Item> {
  return this.getItemList().pipe(
    map(items => items.find(item => item.id === id)),
  );
}

2 Comments

Sorry, I had the wrong name in my code example. I fixed it now. This is one of the things I already tried.
Then your consumer should handle the 'undefined' case
0

I agree with you. Your itemList variable is empty when you call getItemList(). To make sure put debugger and check itemList in getItem() :

  getItem(id: string): Observable<Item> {
    let item: Item;

    console.log(this.itemList);

    debugger;

    // see whether itemList is empty or not;
    // if it's empty, the below function - getItemList() - return an observable with empty value.

    this.getItemList().subscribe(items => {
      console.log('callback called');
      item = items.find(item => item.id === id);
    });
    return of(item);
  }

The point is that getItemList which contains { return of(itemList) } is not going to watch for new value of itemList.

You can instead use subject and use next(data) when data fetching from server got completed and then subscribe to that subject.

Comments

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.