Is it possible to extract the value from a switchMap observable instead of just receiving the observable itself?

I am currently working on creating a unique in-memory singleton that stores the vendor being viewed by a user.

A guard is implemented on all specific routes to capture the parameter:

canActivate(
  route: ActivatedRouteSnapshot,
  state: RouterStateSnapshot): Observable<boolean | UrlTree> {

  let currentUrl = this._router.url;
  const param = route.params['vendname'];

  return this._vendorService.getByName(param).pipe(map(a => {
    if (a == null) {
      this._snackBarService.open('Vendor not found', 'x', { duration: 5000 });
      return this._router.parseUrl(currentUrl);
    }
    return true;
  }));
}

A service is utilized to fetch the vendor by name. If it's present in memory, it's directly returned. Otherwise, it's fetched from the server first.

set vendor(value: IUser) {
  this._vendor.next(value);
}

get vendor$(): Observable<IUser> {
  return this._vendor.asObservable();
}

getByName(name: string): Observable<IUser> {
  const result = this.vendor$.pipe(map(v => {
    if (v != null && v.displayName == name) {
      return v;
    }
    else {
      return this.Get<IUser>(`api/vendor/${name}`).pipe(switchMap(v => {
        this.vendor = v;
        return of(v)
        // ...
      }));
    }
  }))
  return result;
}

The issue lies in the fact that I need to verify vendor$ for its value returning an Obervable<IUser>, but the switchMap also provides an Obervable<IUser>, resulting in

Observable<Observable<IUser>>
. How can I ensure that the result only returns a single User Observable?

Answer №1

It seems there may be a misunderstanding regarding the usage of switchMap(). It is not the reason for your

Observable<Observable<IUser>>
issue. The first map() operator inside the getByName() function is responsible for either returning a value of type IUser (true) or an observable of IUser (false). Therefore, getByName() will return either Observable<IUser> or
Observable<Observable<IUser>>
.

If you are looking to leverage replaying in-memory values from an observable, I recommend using shareReplay(). Here's a suggested pattern for such a scenario.

private vendorName = new Subject<string>();

public vendor$ = this.vendorName.pipe(
  distinctUntilChanged(),
  switchMap(name=>this.Get<IUser>(`api/vendor/${name}`)),
  shareReplay(1)
)

public getByName(name:string){
  this.vendorName.next(name);
}

Then, in the guard file:

canActivate(
  // ...
){
  let currentUrl = this._router.url;
  const param = route.params['vendname'];

  this._vendorService.getByName(param);

  return this._vendorService.vendor$.pipe(
    map(vendor=>{
      if(!vendor){
        this._snackBarService.open('Vendor not found', 'x', { duration: 5000 });
        return this._router.parseUrl(currentUrl);
      }
      return true;
    })
  );

With this setup, your component(s) and guard can subscribe to vendor$ to access the required IUser data. When you have the vendor name to retrieve, simply call getByName(). This will trigger the following steps:

  1. The userName subject will emit the new name.
  2. The vendor$ observable (subscribed to userName) will use that name to fetch the value from the inner observable (the Get method).
  3. The shareReplay(1) ensures that any subscription to vendor$ (current or future) will receive the last emitted value stored in memory.

To update the vendor name, simply call getByName() with the new name, and all subscriptions to vendor$ will automatically reflect the updated value.

Answer №2

in my observation, the vendor$ appears to be a sequence of IUser. To enhance your code, I suggest utilizing the iif rxjs operator for subscribing to either the first or second observable based on a specific condition.

 this.vendor$.pipe(
    mergeMap(v => iif(
      () => v && v.displayName == name, of(v), this.Get<IUser>(`url`).pipe(tap(v => this.vendor = v))
     )
    )
  )

Answer №3

Here is an additional solution that I found effective:

When dealing with the canActivate method that also returns a Promise, you can take advantage of the reliable async and await approach.

It's worth noting that in rxjs 7, the toPromise method has been replaced by lastValueFrom.

async getUserByVendorName(name: string): Promise<IUser> {
  const user:IUser = await new Promise(resolve => { 
    this.vendor$.subscribe(data => {
      if (data != null && data.displayName.toLocaleLowerCase() == name) {
        resolve(data);
      }
    });
    // Resolve with null if no matching data is found
    resolve(null);
  });

  if (user == null) {
    return this.Get<IUser>(`api/vendor/${name}`).pipe(switchMap(result => {
      this.vendor = result;
      return of(result);
    })).toPromise();

  }
  return Promise.resolve(user);
}

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

Angular is giving me a hard time setting my background image

I'm having trouble getting the background image to load on my page. No matter what I try, it just won't show up. Here's the code snippet for my page: <div class="container" [ngStyle]="{'background-image': getUrl()}"> < ...

Using RxJS to merge various HTTP requests into a unified and flattened observable array

Struggling with combining multiple http get requests simultaneously and returning them as a unified, observable array. In my current setup, the method returnNewCars() retrieves Observable<ICar[]> by executing a single http get request. However, in t ...

Why is it that the HttpClient constructor in Angular doesn't require parameters when instantiated through the constructor of another class, but does when instantiated via the 'new' keyword?

I am trying to create a static method for instantiating an object of a class, but I have encountered a problem. import { HttpClient } from '@angular/common/http'; export MyClass { // Case 1 public static init(): MyClass { return this(new ...

What could be causing TypeScript to throw errors when attempting to utilize refs in React?

Currently, I am utilizing the ref to implement animations on scroll. const foo = () => { if (!ref.current) return; const rect = ref.current.getBoundingClientRect(); setAnimClass( rect.top >= 0 && rect.bottom <= window.i ...

Encountering a critical issue with Angular 12: FATAL ERROR - The mark-compacts are not working effectively near the heap limit, leading to an allocation failure due

After upgrading my Angular application from version 8 to 12, I encountered an issue. Previously, when I ran ng serve, the application would start the server without any errors. However, after updating to v12, I started receiving an error when I attempted t ...

default selection in angular 2 dropdown menu

I'm struggling to figure out how to set a default value for a select dropdown list effortlessly. Here's my current code: <select class="form-control" ngControl="modID" #modID="ngForm"> <option *ngFor="let module of modules" [val ...

Unusual behavior exhibited by ng-if within a widget

Hey there, seeking some assistance here. I'm currently making edits to a widget and within the client HTML code, I've come across two ng-if statements (the first one was already there, I added the second). <li> <a ng-if="data.closed ...

Tips for avoiding a React component from causing the page to freeze post-loading

Currently, I am utilizing the uiwjs/react-json-view library to display JSON data. However, there seems to be an issue when attempting to load a large JSON file as it causes the page to freeze. To address this problem, I have already implemented Suspense an ...

In order for the expansion parameter to be successfully used, it must be either of tuple type or passed to the

const myFunction = function(arg1: number, arg2: number, arg3: number) {} const myFunction1 = function() {} const obj = { myFunction, myFunction1 }; type FuncType = typeof obj; function executeFunction<T extends keyof FuncType>(key: ...

Retrieve the data in JSON format including the child elements from a PostgreSQL

I have data from a table in Postgres that I need to be returned in Json format with its children properly ordered. So far, I haven't found a solution to achieve this. Is there a way in PostgreSQL to order the parent modules along with their child modu ...

Implementing generics in TypeScript for objects made easy with this guide!

My question is regarding a function that utilizes generics and selects data from an object based on a key. Can we use generics inside the type of this object, or do we have to create a separate function for options? enum Types { book = 'book', ...

The cdkDropListSortingDisabled attribute is not recognized as a valid property for the cdkDropList directive in Angular Material

I'm trying to achieve a specific use case where I need to drag and drop data from one zone (div) to another. After some research, I discovered that angular/material2 with the cdkDropList API could help me accomplish this task. Using the copyArrayitem ...

Why are the tabs in Angular displaying differently when the tab titles exceed 8 characters with Bootstrap 5?

Angular 14 Bootstrap 5 I developed a custom tabs component with pipes between the tabs that works flawlessly. However, I encountered an issue where the tabs slightly shift when the tab title exceeds 8 characters. Despite my efforts, I cannot pinpoint the ...

A peculiar quirk with Nuxt.js radio buttons: they appear clickable but are somehow not disabled

I’m having an issue with a radio button that won’t check. It seems to be working fine on other pages, but for some reason it just won't click here. <div class="form-group"> <label class="control-label&q ...

What is the best way to retrieve information from my Angular 2 component while I am already within my Kendo Grid?

After creating a new row in my grid, I encounter an issue with accessing other information within my component. Typically, I would use "this.method" or "this.property" to access these details. However, post-creating the row, "this" no longer references t ...

Mastering the application of map, filter, and other functions in Angular using Observables

After creating this Observable: const numbers$:Observable<any>=Observable.create((observer)=>{ for(let i=0;i<5;i++) observer.next(i); }) I attempted to use map and filter as shown below: numbers$.pipe(map(x=>{x+110})).subscr ...

What is the best way to calculate checksum and convert it to a 64-bit value using Javascript for handling extremely large files to avoid RAM overflow?

Question: What is the best method for generating a unique and consistent checksum across all browsers? Additionally, how can a SHA256/MD5 checksum string be converted to 64-bit? How can files be read without requiring excessive amounts of RAM when ...

Navigating through multiple checkbox values in Angular 4/5

Is there a way to retrieve values from checkboxes other than just true or false? Take a look at my template below: <h4>Categories</h4> <div class="form-check" *ngFor="let cat of categories$|async"> <input class="form-check-input" ...

The parameter failed to initialize causing the HTTP service to fire prematurely

In my project, I am utilizing Angular 10. Within the ngOnInit function, I have nested HTTP calls structured like this: ngOnInit(): void { let communetyid; this.route.data.subscribe(data => { this.route.params.subscribe(params => { ...

Apply a border to the div that has been selected

I have a tool for storing information and I am using *ngFor to display each instance in a line. Is there a way to add a border when clicking on a line? The border should only appear on the clicked line, disappearing from the previous one if another line i ...