Tips for elegantly merging two Observables within an RXJS pipeline

I am working on developing a log viewer using Angular.

Upon user entry, I aim to load historical logs and also begin monitoring for new logs. Users have the ability to filter logs using a simple form that emits a query object. Each time the query changes, the process restarts (meaning old results are removed, new historical data is loaded, and a new live stream begins).

I have two possible ways of achieving this, but I am not entirely satisfied with either approach.

The first method seems easier to comprehend but it does not adhere to the DRY principle:

const historicalLogs = this.querySubject
.pipe(
  debounceTime(250),
  tap(() => this.logs = []),
  switchMap(
    query => this.deviceLogsService.getLogsBefore(query, moment())
  )
);

const futureLogs = this.querySubject
.pipe(
  debounceTime(250),
  tap(() => this.logs = []),
  switchMap(
    query => timer(1000, 2000).pipe(mergeMap(t => this.deviceLogsService.getLogsAfter(query, moment())))
  )
);

merge(historicalLogs, futureLogs)
.subscribe(newLogs => {
  this.logs.push(...newLogs);
  this.scrollToVeryBottom();
});

The second method avoids violating DRY principles, but may be challenging to understand or analyze in the future:

this.querySubject
  .pipe(
    debounceTime(250),
    tap(() => this.logs = []),
    switchMap(query => concat([
      this.deviceLogsService.getLogsBefore(query, moment()),
      timer(1000, 2000).pipe(mergeMap(t => this.deviceLogsService.getLogsAfter(query, moment())))
    ]).pipe(mergeAll()))
  )
  .subscribe(newLogs => {
    this.logs.push(...newLogs);
    this.scrollToVeryBottom();
  });

I am open to any suggestions on how to implement this functionality in a more elegant and readable manner.

Answer №1

If you want to simplify your code, one approach is to create two functions that handle fetching logs from both the past and future:


private getHistoricalLogs(query) {
  return this.deviceLogsService.getLogsBefore(query, moment());
}

private pollForFutureLogs(query) {
  return timer(1000, 2000).pipe(
    switchMap(() => this.deviceLogsService.getLogsAfter(query, moment()))
  );
}

this.querySubject.pipe(
    debounceTime(250),
    tap(() => this.logs = []),
    switchMap(query => concat([
      this.getHistoricalLogs(query),
      this.pollForFutureLogs(query)
    ]))
  .subscribe(newLogs => {
    this.logs.push(...newLogs);
    this.scrollToVeryBottom();
  });

Instead of managing a separate logs variable outside of the stream, you can emit an empty array at the beginning and concatenate all emissions into a single array using scan:

logs = this.querySubject.pipe(
    debounceTime(250),
    switchMap(query => concat([
      of([]),
      this.getHistoricalLogs(query),
      this.pollForFutureLogs(query)
    ])),
    scan((all, logs) => all.concat(logs), [])
);

logs.subscribe(() => this.scrollToVeryBottom());

Answer №2

Did you consider utilizing combineLatest?:

obs$.pipe(
  ...,
  switchMap(query => combineLatest([
    this.dataService.fetchDataBefore(query, moment()),
    timer(1000, 2000).pipe(mergeMap(t => this.dataService.fetchDataAfter(query, moment())))
  ])),
  map(([dataBefore, dataAfter]) => {...})
)

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

Assign a property to an array of objects depending on the presence of a value in a separate array

Looking to manipulate arrays? Here's a task for you: const arrayToCheck = ['a', 'b', 'c', 'd']; We have the main array as follows: const mainArray = [ {name:'alex', code: 'c'}, ...

Retrieving information from a data file by implementing a GraphQL Apollo Server within a NextJS application route

Currently working with Next.js 14 (app route), React, and the GraphQL Apollo framework. I have a JSON file containing data saved locally that I'd like to display using the server API. How can I make this happen? Below is the JSON structure I need to r ...

What is the recommended data type for the component prop of a Vuelidate field?

I'm currently working on a view that requires validation for certain fields. My main challenge is figuring out how to pass a prop to an InputValidationWrapper Component using something like v$.[keyField], but I'm unsure about the type to set for ...

Encountering a 405 error when making an OpenAI API call with next.js, typescript, and tailwind CSS

I encountered a 405 error indicating that the method request is not allowed. I am attempting to trigger an API route call upon clicking a button, which then connects to the OpenAI API. Unsure of my mistake here, any guidance would be highly appreciated. E ...

What is the method for including word boundaries in a regex constructor?

export enum TOKENS { CLASS = 1, METHOD, FUNCTION, CONSTRUCTOR, INT, BOOLEAN, CHAR, VOID, VAR, STATIC, FIELD, LET, DO, IF, ELSE, WHILE, RETURN, TRUE, FALSE, NULL, THIS } setTokenPatterns() { let tokenString: s ...

Changes on services do not affect the Angular component

Currently facing an issue with my Angular assignment where changing an element's value doesn't reflect in the browser, even though the change is logged in the console. The task involves toggling the status of a member from active to inactive and ...

Can you explain the rule known as the "next-line" in TypeScript?

No code examples are available for the specific scenario described below: "next-line": [ true, "check-catch", "check-finally", "check-else", "check-open-brace", "check-whitespace" ], ...

What is the best way to update the displayed data when using Mobx with an observable array?

Is there a way to re-render observable array data in Mobx? I have used the observer decorator in this class. interface IQuiz { quizProg: TypeQuizProg; qidx: number; state: IStateCtx; actions: IActionsCtx; } @observer class Comp extends Rea ...

Is there a workaround for the React useContext issue in Typescript aside from using <Partial>?

I am currently working on a React app that utilizes the useContext hook, but I am facing challenges with correctly typing my context. Here is the code snippet in question: import React, { useState, createContext } from 'react'; import endpoints f ...

Navigate to a new tab using this.router.navigate

Is there a way to redirect the user to a specific page with ${id} opening in a new tab, after clicking a button in an angular material dialog box? I want to leave the dialog box open while querying the new page. Currently, the redirect happens but not in a ...

What is the significance of the colon before the params list in Typescript?

Consider the following code snippet: import React, { FC } from "react"; type GreetingProps = { name: string; } const Greeting:FC<GreetingProps> = ({ name }) => { // name is string! return <h1>Hello {name}</h1> }; Wha ...

Backend data not displaying on HTML page

I am currently working on an Angular 8 application where I have a service dedicated to fetching courses from an API endpoint. The service method that I'm using looks like this: loadCourseById(courseId: number) { return this.http.get<Cours ...

Disregarding TypeScript import errors within a monorepo ecosystem

In my Turborepo monorepo, I have a Next.js app package that imports various components from a shared package. This shared package is not compiled; it simply contains components imported directly by apps in the monorepo. The issue arises with the shared co ...

Updating events instantly with a single click in Angular 6: A step-by-step guide

Hello there, I am currently diving into learning Angular 6 and I have encountered a query. I want to achieve a functionality where upon clicking a button, the text on the button changes as well as the corresponding event that triggers when the button is cl ...

Why am I encountering this rendering issue when passing data to the ReactTable component?

The following code snippet represents the parent component containing an array of columns and data. const TransactionTable = () => { const columns = useMemo( () => [ { Header: 'DATE/TIME', accessor: &apos ...

The error "Property 'push' does not exist on type '() => void'" occurs with Angular2 and Typescript arrays

What is the method to initialize an empty array in TypeScript? array: any[]; //To add an item to the array when there is a change updateArray(){ this.array.push('item'); } Error TS2339: Property 'push' does not exist on type &a ...

Conceal Primeng context menu based on a certain condition

I'm struggling to prevent the context menu from showing under certain conditions. Despite following the guidelines in this post, the context menu continues to appear. My goal is to implement a context menu on p-table where it should only show if there ...

What sets apart the utilization of add versus finalize in rxjs?

It appears that both of these code snippets achieve the same outcome: Add this.test$.pipe(take(1)).subscribe().add(() => console.log('added')); Finalize this.test$.pipe(take(1), finalize(() => console.log('finalized'))).sub ...

Using Typescript and JSX to render a component that has been passed as an argument

I am seeking to create a function that will render a React component passed as an argument. I aim to accommodate both Component and StatelessComponent types with the following approach: function renderComponent(component: React.ComponentClass<any> | ...

Passing layout to a Vue component using the setup script

LayoutComponent <template> //some code here ... <div> <slot></slot> </div> </template> In the composition api, it is possible to pass a layout by importing it and then passing it into t ...