Combining two streams in RxJS and terminating the merged stream when a particular input is triggered

I am currently developing an Angular application and working on implementing a system where an NGRX effect will make requests to a service.

This service will essentially perform two tasks:

  • Firstly, it will check the local cache (sqlite) for the requested data,
  • Then, it will contact the API to retrieve the most recent data

My goal is to define an observable response from this service that will either emit the API's response alone (in case of a cache miss) or emit the local data followed by the API data. I have illustrated these two scenarios below:

Scenario 1 (API request is first/cache miss)

API   ---1
CACHE ---------2
MERGE ---1

Scenario 2 (Cache hit)

API   ---------1
CACHE ---2
MERGE ---2-----1

I am considering creating a Subject, triggering both calls, and completing the Subject when the API responds (whether successfully or with an error).

Pseudo code:

const subject = new Subject();

this.cache.getData().then(res => subject.next(res)).catch(err => subject.error(err));
this.api.getData().then(res => subject.next(res)).catch(err => subject.error(err)).finally(() => subject.complete());

return subject.asObservable();

However, I am curious if there is a more elegant solution to achieve what I need here. Any suggestions would be greatly appreciated.

Answer №1

If my understanding is correct, you are looking to make two calls - A) API, B) Cache. If A returns first, emit that result without waiting for the response from B. If B returns first, emit that result and then also emit the response from A when it arrives.

Basically, you always want a response from A. You only want a response from B if it is received before A.

To achieve this, you can define two source observables and use the merge operator.

private apiData$ = defer(() => this.api.getData()).pipe(share());

private cachedData$ = defer(() => this.cache.getData()).pipe(
  filter(existing => !!existing),
  takeUntil(apiData$)
);

data$ = merge(this.cachedData$, this.apiData$);

In this solution, we utilize defer to convert Promises into Observables upon subscription.

apiData$ uses share() to prevent duplicate API calls as we will have two subscriptions.

cachedData$ applies a filter to exclude emissions when data does not exist in the cache.

The takeUntil operator ensures that the cachedData$ observable completes once api$ emits a value, preventing further emissions from cachedData$ after that point.

Lastly, we use merge to combine both sources into a single observable, so that data$ emits whenever either source emits.


You can view a demo on StackBlitz.

Similar questions

If you have not found the answer to your question or you are interested in this topic, then look at other similar questions below or use the search

When updating the data in a datatables.net table within Angular 7, the previous data from the initial table load is retained

I'm currently working on a project that involves live reporting from a REST API request using the datatables.net library in Angular 7. When I update data in the database, the table reflects these changes accurately. However, if I interact with the tab ...

Proper positioning of try/catch block in scenarios involving delayed async/await operations

For the past six months, I have been utilizing async/await and have truly enjoyed the convenience it provides. Typically, I adhere to the traditional usage like so: try { await doSomethingAsync() } catch (e) {} Lately, I've delved into experimenti ...

What are the steps for personalizing themes in the Monaco editor?

I'm currently working on a code editor with Monaco. The syntax highlighting in Monaco for Javascript and Typescript only highlights keywords as dark blue, strings as brown, and numbers as light greenish-yellow. My goal is to customize the vs-dark the ...

Cypress - AG-Grid table: Typing command causing focus loss in Cell

Encountering a peculiar issue: I am attempting to input a value into the cell using 'type()'. My suspicion is that each letter typed causes the focus on the cell to be lost. It seems that due to this constant loss of focus and the 'type()& ...

Struggling with Primeng's KeyFilter functionality?

I've implemented the KeyFilter Module of primeng in my project. Check out the code snippet below: <input type="text" pInputText [(ngModel)]="price.TintCost" [pKeyFilter]="patternDecimal" name="tintCost" required="true" /> Take a look at my Typ ...

Issue with bi-directional data binding in Angular's matInput component

When working on my template... <input matInput placeholder="Amount" [(value)]="amount"> In the corresponding component... class ExampleComponent implements OnInit { amount: number = 0; ... } The binding doesn't seem to work as expect ...

HammerJS swipe functionality does not seem to be functioning properly on elements that have the overflow CSS

UPDATE: The code snippet works smoothly when embedded in the question, but encounters issues when edited. This led me to discover that the problem lies with underlying containers that require scrolling... After testing it on my phone, I found that Hammer f ...

When utilizing express-handlebars to render, the error message "req.next() is not a valid function

Trying to display the login page of a web application. Developed using TypeScript, node.js, express, and express-handlebars The code being executed is as follows: import exphbs = require("express-handlebars"); import cookieParser = require(&quo ...

Using TypeScript to Implement Content Security Policy Nonce

I encountered an issue with my TypeScript Express project while attempting to implement a CSP Nonce using Helmet. app.use(helmet.contentSecurityPolicy({ useDefaults: true, directives: { scriptSrc: ["'self'", (req, res) = ...

Implementing experimental decorators and type reconciliation in TypeScript - A step-by-step guide

My basic component includes the following code snippet: import * as React from 'react'; import { withRouter, RouteComponentProps } from 'react-router-dom'; export interface Props { }; @withRouter export default class Movies extends R ...

When working with Angular and NGRX, I often wonder if opening my app in multiple tabs within the same browser will result in all tabs pointing to a single NGRX instance

Is it possible to access my localhost:9000 in multiple tabs? If I open it in 3 tabs, will all those 3 tabs point to a single NGRX instance or will each tab of localhost have its dedicated NGRX instance? I haven't had the chance to test this yet. ...

What is the best way to monitor updates made to a function that utilizes firestore's onSnapShot method?

I am currently working with the following function: public GetExercisePosts(user: User): ExercisePost[] { const exercisePosts = new Array<ExercisePost>(); firebase.firestore().collection('exercise-posts').where('created-by&apo ...

Instance property value driven class property type guard

Is it possible to create a class example that can determine the config type based on the value of animalType instance: enum Animal { BIRD = 'bird', DOG = 'dog', } type Base = { id: number } // Object example type Smth = Base & ...

Is this Firebase regulation accurate and suitable for PUT and GET requests in an Angular and Firebase environment?

Creating a system where users can only see their own posts and no one else can access them is my main goal. Authentication along with posting functionality is already in place and working, but I want to implement this without using Firebase restrictions. ...

Tips for bundling and inlining .json files within an Angular npm package

I am currently in the process of developing an NPM Package for an Angular module that I intend to utilize across several Angular applications. The Angular module I have developed relies on ng2-translate to access localization strings stored in .json file ...

AmplifyJS is throwing an error: TypeError - It seems like the property 'state' is undefined and cannot be read

I am currently working on integrating the steps outlined in the Amplify walkthrough with an Angular cli application. My app is a brand new Angular cli project following the mentioned guide. My objective is to utilize the standalone auth components a ...

Ways to apply the strategy pattern in Vue component implementation

Here's the scenario: I possess a Cat, Dog, and Horse, all of which abide by the Animal interface. Compact components exist for each one - DogComponent, CatComponent, and HorseComponent. Query: How can I develop an AnimalComponent that is capable of ...

Having trouble retrieving the selected dropdown value in Angular 5 using <p-dropdown> from PrimeNG due to issues with the ngOnInit function. Need some assistance with

My values are currently being populated in the ngOnInit() method, so the event is not available there. I need to retrieve the previously selected value even after refreshing the page. <p-dropdown [options]="Options" [(ngModel)]="Id" (onChange)="onChang ...

Can someone provide guidance on effectively implementing this JavaScript (TypeScript) Tree Recursion function?

I'm currently grappling with coding a recursive function, specifically one that involves "Tree Recursion". I could really use some guidance to steer me in the right direction. To better explain my dilemma, let's consider a basic example showcasi ...

What is the most appropriate form to use, and what factors should be considered in determining

Incorporating generics in typescript allows me to create a generic function in the following manner: Choice 1 declare function foo1<T, K extends keyof T>(obj: T, key: K): T[K] { return obj[key]; } Alternatively, I have the option to omit the seco ...