Artem
Artem

Reputation: 1870

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...

Upvotes: 3

Views: 155

Answers (2)

Mrk Sef
Mrk Sef

Reputation: 8052

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.

Upvotes: 4

Joshua McCarthy
Joshua McCarthy

Reputation: 1852

You can declare a fetchBook$ observable, and a patchBook$ subject. Then your book$ observable can be a merge of the two.

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

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

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

const patchBook$ = Subject<{ bookId: number, newTitle: string}>();

const book$ = merge(fetchBook$, patchBook$);


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

bookId$.next(1);
patchBook(2, 'Moby Dick');

Upvotes: 1

Related Questions