Leveraging the power of Angular's Rxjs Observables in conjunction with the n

Currently, I am working on a project where I'm utilizing Angular 7 along with ngrx store to manage the app state. In my components, I am subscribing to the app state in the OnInit method. Within the store, there are multiple variables that can be swapped out using a button.

Below is an example code snippet from one of my components:

this.appState.getGasStationSushi().pipe(switchMap((sushi) => {
  this.gsSushi = sushi;
  return this.appState.getStarbucksSushi();
}), switchMap((sushi) => {
  this.sbSushi = sushi;
  return this.service.compare(this.gsSushi, this.sbSushi);
}).subscribe((result)=>{
  console.log(result);
}));

Upon clicking a button in the view, the user has the ability to update both sushi values, resulting in the final subscription being called twice due to the behavior of RxJS. I've considered removing the switchMap and structuring the code differently as follows:

-- gas station sushi subscription 
   -- star bucks sushi subscription 
      -- compare

However, I believe there may be a more streamlined approach using an rxjs/operator. Any suggestions would be greatly appreciated!

I also experimented with using forkjoin, but encountered issues when working with the ngrx store. It seems like I need to use 'first' or 'last' as shown below. For further details, you can refer to this link: forkjoinWithstore

const $sushiObs = [
  this.appState.getGasStationSushi().pipe(first()),
  this.appState.getStarbucksSushi().pipe(first())
];
forkjoin($sushiObs).subscribe((result) => {
  console.log(result);
});

When implementing the above pattern, the subscriptions fire initially but do not work thereafter.

Answer №1

Here is a functional demonstration on stackblitz.

I decided to create a mock class called SushiState instead of utilizing a store, which returns observables.

class SushiState {
  private _gas = new BehaviorSubject(1);
  private _starbucks = new BehaviorSubject(1);

  public get gas() {
    return this._gas.asObservable();
  }
  public get starbucks() {
    return this._gas.asObservable();
  }

  public increaseSushi(n = 1) {
    this._gas.next(this._gas.value + n);
    this._starbucks.next(this._starbucks.value + n);
  }

  public static compareSushi(g: number, s: number): string {
    return `gas is ${g}, starbucks is ${s}`;
  }
}

Below you can find the code for the component.

export class AppComponent implements OnInit {
  state: SushiState;

  gasSushi: Observable<number>;
  sbSushi: Observable<number>;

  combined: string;
  combinedTimes = 0;
  zipped: string;
  zippedTimes = 0;

  ngOnInit() {
    this.state = new SushiState;
    this.gasSushi = this.state.gas;
    this.sbSushi = this.state.gas;

    const combined = combineLatest(
      this.gasSushi,
      this.sbSushi,
    ).pipe(
      tap((sushi) => {
        console.log('combined', sushi);
        this.combinedTimes++;
      }),
      map((sushi) => SushiState.compareSushi(sushi[0], sushi[1])),
    );
    combined.subscribe(result => this.combined = result);

    const zipped = zip(
      this.gasSushi,
      this.sbSushi,
    ).pipe(
      tap((sushi) => {
        console.log('zipped', sushi);
        this.zippedTimes++;
      }),
      map((sushi) => SushiState.compareSushi(sushi[0], sushi[1])),
    );
    zipped.subscribe(result => this.zipped = result);
  }

  increaseSushi() {
    this.state.increaseSushi();
  }

}

If you run it on stackblitz and monitor the console, you will see the following activity:

https://i.sstatic.net/slFto.png

When using combine latest, we merge the observables individually and only focus on the most recent state, resulting in 2 instances of console.log.

An alternative approach would be to utilize zip, which waits for both observables to emit before generating an output. This seems suitable for our "Increase Both" button, however, if the starbucksSushi gets incremented separately (perhaps from another section of the application), the 'zipped' version will wait for the gas station sushi to also update.

A third solution suggestion involves using combineLatest to unite the sushi counters, then implementing the debounceTime operator to delay emitting the output for a set number of milliseconds.

const debounced = zip(
  this.gasSushi,
  this.sbSushi,
).pipe(
  tap((sushi) => {
    console.log('debounced', sushi);
    this.debouncedTimes++;
  }),
  map((sushi) => SushiState.compareSushi(sushi[0], sushi[1])),
  debounceTime(100),
);
debounced.subscribe(result => this.debounced = result);

This approach reacts to changes in all sources, but not more frequently than once per 100ms.

The need for first() was due to...

forkJoin merges the observables after they complete (which occurs only once, making it unsuitable for continuous streams) and is better suited for tasks resembling promises like HTTP calls or process completions. Additionally, when extracting just one element from a stream, the resultant stream completes after the single emission.

P.S.

It is recommended to utilize the async pipe for handling observables (as demonstrated with properties such as

gasSushi: Observable<number>;
sbSushi: Observable<number>;

and within the template like so

<div>
  <h3>starbucks sushi</h3>
  <p>{{sbSushi | async}}</p>
</div>

instead of manually subscribing as in

result => this.zipped = result

In this example, both methods are used for comparison. In my experience, working with observables becomes much simpler once you cease prematurely unsubscribing and let the async pipe handle subscriptions.

Furthermore, if you do employ subscribe in your component, ensure to unsubscribe upon component destruction - although this is straightforward, by abstaining from explicit subscriptions and leaving them to the async pipe, it automatically manages cleanup for us :)

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

Retrieve a type from a Union by matching a string property

Looking at the following structure: type Invitation = | { __typename?: 'ClientInvitation' email: string hash: string organizationName: string } | { __typename?: 'ExpertInvitation' email: strin ...

Struggling with Angular Fire and Firebase Emulators in Updating Documents

Following the update of Angular Fire and Firebase Emulators to their latest versions, we encountered an issue where updating a document is no longer functioning. Despite being able to create new documents without any complications, both .update() and set() ...

Encountered issues with the BsModalService in displaying the modal functionality

I am encountering an issue while attempting to display a modal using a service. When I call the service from a button, everything works fine. However, I encounter an error when calling it from an error handler. TypeError: Cannot read property 'attach ...

What is the reason for not hashing the password in this system?

My password hashing code using Argon2 is shown below: import { ForbiddenException, Injectable } from '@nestjs/common'; import { PrismaService } from 'src/prisma/prisma.service'; import { AuthDto } from './dto'; import * as arg ...

Modify the background color of a clicked event in Angular Full Calendar

Is it possible to modify the background color of an event when it is clicked on in Angular FullCalendar? I attempted to do so by setting the event backgroundColor property within the click event, but the changes were not reflected in the view. eventClick ...

The debate between TypeScript default generic types and contextual typing

Contextual Typing in TypeScript is an interesting feature where the correct type is inferred from the return value when a generic type is not specified. While it usually works well, there are instances where it can be unpredictable. For example, in some c ...

What steps should be taken to rectify the issue in the Angular HttpClient concerning the delete method?

(English is not my first language so please forgive me) Hello, I am new to Angular and I am attempting to make an HTTP request that deletes a doctor when a button is clicked. However, I am struggling with what steps I need to take to get my code working pr ...

What is the best way to define an object type using a string as its name?

Below is an example array const roads = [ "Alice's House-Bob's House", "Alice's House-Cabin", "Alice's House-Post Office", ... ]; I'm looking to create a type Graph which should be an ...

The Angular translation service may encounter issues when used on different routes within the application

I'm currently working on an Angular application that has multi-language support. To better organize my project, I decided to separate the admin routes from the app.module.ts file and place them in a separate file. However, after doing this, I encounte ...

Structuring an Angular 2 Project

Embarking on a new project in Angular2, I find myself pondering the optimal structure for an Angular2 application. Imagine I have various pages such as home, auto-galleries, nearest-galleries, brands, cars, and selected-car. The navigation sequence could b ...

fesm2020/ngx-translate-multi-http-loader.mjs:2:0-41 - Whoops! Looks like the module couldn't be located

After updating my ngx-translate-multi-http-loader from version 8.0.2 to 9.3.1, I encountered a module not found error. The error message is as follows: ./node_modules/ngx-translate-multi-http-loader/fesm2020/ngx-translate-multi-http-loader.mjs:2:0-41 - Err ...

Stay updated with Angular/RxJS by subscribing to keys and observables!

While going through the documentation for Angular and RxJS, I couldn't find an answer to this specific query. When I make a HttpClient request in Angular and it returns an observable, like so: let request = this.http.get(url); request.subscribe( { ...

Unable to resolve TypeScript error: Potential 'undefined' object

Here is the code snippet that I am working with: const queryInput = useRef() const handleSubmit = (e: FormEvent<HTMLFormElement>) => { e.preventDefault() if (queryInput && queryInput.current) { console.log(`queryInput.cur ...

What is the reason for the transparency of the angular material modal?

Having an issue with my angular material modal: runProcess(assignmentNumber) { const dialogConfig = new MatDialogConfig(); dialogConfig.autoFocus = true; dialogConfig.data = { assignmentNumber } this.dialog.open(RunPostReleaseProcessCompon ...

Using 'interface' declarations from TypeScript is unsupported in JS for React Native testing purposes

I have a ReactNative app and I'm attempting to create a test using Jest. The test requires classes from a native component (react-native-nfc-manager), and one of the needed classes is defined as follows export interface TagEvent { ndefMessage: N ...

Efficient code for varying layouts of disabled Angular Material buttons

I've been wondering if there's a simpler way to customize the appearance of a disabled button using Angular Material. Currently, I achieve this by utilizing an ng-container and ng-template. While this method gets the job done, the code is not ve ...

What methods are typically used for testing functions that return HTTP observables?

My TypeScript project needs to be deployed as a JS NPM package, and it includes http requests using rxjs ajax functions. I now want to write tests for these methods. One of the methods in question looks like this (simplified!): getAllUsers(): Observable& ...

Here's how to retrieve a property from a union type object in Typescript without the need for type casting

I am facing a scenario with various types: export type a = { todo: string; }; export type b = { id: number; }; export type TodosAction = Action<string> & (a | b); In addition, I have a function defined as follows: function doSmth(action:To ...

When making a variable call outside of a subscriber function, the returned value is 'undefined'

I find myself in a situation where I have to assign a value to a variable inside a subscriber function in Angular. The issue is that the variable returns 'undefined' when called outside of the Subscribe function. Here's what I'm encount ...

Using the prop callback in a React test renderer does not trigger updates in hooks

I am currently exploring ways to effectively test a React function component that utilizes hooks for state management. The issue I am encountering revolves around the onChange prop function not properly updating the state value of my useState hook. This in ...