Search code examples
rxjsrxjs6rxjs-observables

What is the best way to patch fetched objects in RxJS?


I have a code that fetches book by its id

const fetchBook = (bookId: number) => {
    const title = 'Book' + bookId;
    // mimic http request
    return timer(200).pipe(mapTo({ bookId, title }));
}

const bookId$ = new Subject<number>();

const book$ = bookId$.pipe(
    switchMap(bookId => fetchBook(bookId)),
    shareReplay(1)
);

book$.subscribe(book => console.log('book title: ', book.title))

bookId$.next(1);

I have an API method that patches values and returns the updated object:

const patchBook = (bookId: number, newTitle: string) => {
    return timer(200).pipe(mapTo({ bookId, title: newTitle }));
}

What should I do to get book$ to emit the new value after I call patchBook(1, 'New Book Title')?

I can declare book$ as Subject explicitly and update it manually. But it will be imperative, not reactive approach.

Upd: The patch is called as a result of user action at any time (or never)

Upd2: Actually book$ can be also changed on server side and my real code looks like this:

const book$ = combineLatest([bookId$, currentBookChangedServerSignal$]).pipe...

Solution

  • The same thing you did to transform a bookId into a Book, you can use to transform a Book into a patchBook.

    const book$ = bookId$.pipe(
        switchMap(bookId => fetchBook(bookId)),
        mergeMap(({bookId, title}) => patchBook(bookId, title)),
        shareReplay(1)
    );
    

    Update:

    patch is not always called

    There are many ways this could be done and the "best" way really depends on how you've architected your system.

    Lets say you dynamically create a button that the user clicks and this triggers an update event.

    const patchBtn = document.createElement("button");
    const patchBook$ = fromEvent(patchBtn, 'click').pipe(
      switchMap(_ => patchBook(bookId, title))
    );
    
    const basicBook$ = bookId$.pipe(
        switchMap(bookId => fetchBook(bookId))
    );
    
    const book$ = merge(patchBook$, basicBook$).pipe(
      shareReplay(1)
    );
    

    You probably want your fromEvent events to emit some data rather then hard-coding (bookId, title) into the stream from a click, but you get the idea. That's just one of many ways to get the job done.

    And of course, it should almost always be possible (and desirable) to remove bookId$, and replace it with a more reactive-style mechanism that hooks declarativly into whatever/wherever the ID's come from in the first place.