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

React component's state is not being correctly refreshed on key events

Currently facing an issue that's puzzling me. While creating a Wordle replica, I've noticed that the state updates correctly on some occasions but not on others. I'm struggling to pinpoint the exact reason behind this discrepancy. Included ...

Angular is set up to showcase every single image that is stored within an array

I am having trouble displaying the images from the "image_url" array using a for loop. The images are not showing up as expected. Here is the content of the array: image_url: [ 0: "https://xyz/16183424594601618342458.5021539.jpg" 1: "https://xyz/1618342459 ...

Is the RouterModule exclusively necessary for route declarations?

The Angular Material Documentation center's component-category-list imports the RouterModule, yet it does not define any routes or reexport the RouterModule. Is there a necessity for importing the RouterModule in this scenario? ...

Encountering an error message stating "The variable 'App' is declared but not used" when running the index.tsx function

This React project is my attempt to learn how to use a modal window. The tutorial I've been following on YouTube uses JavaScript instead of TypeScript for their React project. However, I'm facing some challenges. Could you possibly help me ident ...

Issue with Angular Datatable not displaying content when [dtTrigger] directive is included in the HTML table - Angular 14

Upon adding [dtTrigger] = "dtTrigger" to the HTML table, I encountered an issue where the datatable ceased rendering. All the features of the datatable were no longer visible on the page. Removing [dtTrigger] = "dtTrigger" from the HTML ...

Using `it` with accessing class members

When testing whether a specific object/class is correctly wired up, I often utilize it.each to prevent writing repetitive tests. The issue arises when the type of the object doesn't have an index signature, requiring me to cast it to any for it to fun ...

Nesting two ngFor loops in Angular using the async pipe leads to consistent reloading of data

In my Angular application, I am trying to retrieve a list of parent elements and then for each parent, fetch its corresponding children (1 parent to n children). Parent Child1 Child2 Parent Child1 Parent3 Child1 Child2 Child3 Initially, I succes ...

What could be causing TypeORM to create an additional column in the query

Why does this TypeORM query produce the following result? const result6 = await getConnection() .createQueryBuilder() .select('actor.name') .from(Actor,'actor') .innerJoin('actor.castings',&apos ...

Is there a way to display only the year in the Angular ngx boostap 2.0.5 datepicker?

My goal is to display only the year in the datepicker, but when I select a year, the month appears afterwards. How can I adjust this? ...

Uncover the content of a base64 encoded string and convert it into

A JSON response has been linked on the user's request to retrieve an excel document. The structure of the response is as follows: { "format": // file extn ----only xls "docTitle": //file name "document" :// base 64 encoded data } The attem ...

Versions of Angular that are compatible with Ionic 2 (do not have an exported member)

How do I determine the compatible Angular version for each Ionic version? I keep encountering errors like "has no exported member." For example: The module ".../node_modules/@angular/core/index" does not have an exported member called InjectionToken. The ...

Enhancing Readability of Public Static Member Variables in Typescript

In my node application, I am utilizing typescript and winston for logging purposes. One key element of my setup is the "Logger" class which consists of a "logger" member and an "init()" function. By exporting this class, I understand that the "logger" memb ...

Angular2: Unusual behavior when using angular-http Headers

Currently, I am working on a simple Angular 2 web application and encountering some challenges with HTTP headers... This is the function causing the issue: postStockTake(stockTakeModel: StockTakeModel) : Observable<Response> { let body = JSON.strin ...

What is the process for injecting a template into the Kendo Upload's template?

Apologies if this question has already been addressed, but I am struggling to grasp the concept or find the answer. I am diving into templating in Angular (2/4) and seeking some assistance. I have developed a component that utilizes the kendo upload compo ...

Leverage the power of Firebase Firestore by seamlessly integrating full-text search capabilities with external services while also

While I understand that using external services like Algolia and Elasticsearch is necessary for full-text queries in Firestore, my struggle lies in integrating these tools with Firestore's existing querying options such as "where," "limit," and "start ...

Containerizing Next.js with TypeScript

Attempting to create a Docker Image of my Nextjs frontend (React) application for production, but encountering issues with TypeScript integration. Here is the Dockerfile: FROM node:14-alpine3.14 as deps RUN apk add --no-cache tini ENTRYPOINT ["/sbin ...

Mastering the Art of Promises in RXJS Observables

After thoroughly researching SO, I stumbled upon numerous questions and answers similar to mine. However, I suspect that there might be gaps in my fundamental understanding of how to effectively work with this technology stack. Currently, I am deeply enga ...

tinyMCE5 has a quirky habit of inserting p tags without warning, but only when using

My editor with tinymce5 works well on Chrome and other browsers, but in Mozilla Firefox each line in the editor gets an additional p tag. I am using it with Angular. Currently, I have initialized the editor like this: <editor name="resultEditor" ...

Guide on Generating Dynamic JSON to Set and Retrieve Values for Forms and Displaying the Bound Values

I'm a beginner in Ionic 3 and I'm having trouble creating a page that redirects to another page without validation. I want to display the data on the new page, but it's not working. Please help! I also want to create a dynamic JSON object a ...

Guide to invoking an API in Next.js 13 by utilizing specific variables within a client component

I currently have a collection of products that are accessible on my website through a straightforward function within a server component. async function getData() { const res = await fetch(`${apiPath}`); const data = (await res.json()) as PackProps ...