Sign up for the observable, retrieve the asynchronous mapped outcome with input from the dialog, and then utilize the outcome from the map

Currently, I am utilizing an API-service that delivers an Observable containing an array of elements.

apiMethod(input: Input): Observable<ResultElement[]>

Typically, I have been selecting the first element from the array, subscribing to it, and then using that specific element to navigate to another page in this manner:

this.apiService
  .apiMethod(input)
  .pipe(map((results) => results[0])
  .subscribe(
    (result) => {
      return this.router.navigate('elements/', result.id)
    }
  )

This method has been successful thus far.

However, my goal is not merely to use the first element. Instead, I would like a MatDialog or similar popup to appear, allowing the user to select which element to proceed with before routing to the appropriate one.

In cases where the list contains only one element, the dialog should be omitted, and the user should be routed immediately.

I attempted to implement a dialog within the .pipe(map()) function but encountered issues as the subscribe() occurs before receiving the user's response, leading to failure. I am uncertain if this approach is correct. How would you go about solving this dilemma?

Edit I partially followed @BizzyBob's suggestion:

By substituting map with switchmap in the API-call, the code looks like this:

this.apiService
  .apiMethod(input)
  .pipe(switchMap((results) => this.mapToSingle(results))
  .subscribe(
    (result) => {
      return this.router.navigate('elements/', result.id)
    }
  )

The mapToSingle(ResultElement[]) function is structured as follows:

private mapToSingle(results: ResultElement[]): Observable<ResultElement> {
  if (results.length === 1){
    return of(results[0]);
  }
    const dialogConfig = new MatDialogConfig<ResultElement[]>();
    dialogConfig.data = results;

    const dialogRef = this.dialog.open(ResultDialogComponent, dialogConfig);

    return dialogRef.afterClosed();
}

Answer №1

To streamline the process, I propose developing a custom DialogComponent that accepts a list of options as input and dispatches the selected item upon closure.

Furthermore, you can create a utility function (perhaps named promptUser) which generates an observable to emit the chosen value:

this.apiService.apiMethod(input)
    .pipe(
        switchMap(results => results.length > 1
            ? this.promptUser(results)
            : of(results[0])
        )
    )
    .subscribe(
        result => this.router.navigate('elements/', result.id)
    );

By utilizing switchMap, we can efficiently return an observable containing the appropriate selection. If the array length exceeds 1, the dialog display method is invoked to obtain user input; otherwise, the first (and only) item is emitted. Note the use of of to convert a plain value into an observable within switchMap.

In either scenario, the desired item is successfully emitted and captured by your subscribe function.

Answer №2

Here are two potential approaches:

  1. Include a subject for the chosen result that is determined either by user input or as a consequence of obtaining an api result with just one element.
  2. Maintain an overarching state for the component and react accordingly whenever a selectedResult is assigned in the state.

The following example illustrates using an Observable to monitor the component's state.

  • There are two streams of input into the state: results from the API and user input for the chosen result.
  • Each stream is transformed into a reducer function that will adjust the overall state.
  • The UI should observe this state through an async pipe, displaying the modal when necessary, and updating the state from events via the Subjects.
  • The redirection should occur as a result of changing the state when selectedResult has a value.
readonly getResultsSubject = new Subject<MyInput>();
readonly resultSelectedSubject = new Subject<ResultType>();

private readonly apiResults$ = this.getResultsSubjects.pipe(
  switchMap((input) => this.apiMethod(input))
);

readonly state = combineLatest([
  this.apiResults$.pipe(map(results => (s) => results.length === 1 
    ? { ...s, results, selectedResult: x[0], showModal: false }
    : { ...s, results, showModal: results.length > 1 })),
  this.resultSelectedSubject.pipe(map(selectedResult => (s) => ({ ...s, selectedResult })))
]).pipe(
  scan((s, reducer) => reducer(s), { }),
  shareReplay(1)
);

ngOnInit() {
  this.state.pipe(
    filter(x => !!x.selectedResult)
  ).subscribe(x => this.router.navigate('elements/', x.selectedResult.id));
}

I've recently been using this methodology frequently. It simplifies managing the increasing number of actions and properties of the state.

Answer №3

In my approach to solving this issue, I would follow these steps:

  1. Retrieve the data from the subscription (excluding the pipe) and store it in a component variable
options: any;

this.apiService
  .apiMethod(input)
  .subscribe(
    (result) => {
       if (result.length === 1) {
         this.router.navigate([result[0]]);
         return;
       }
       options = result;
  }
)
  1. Utilize an ngIf directive on the modal to conditionally display the component based on the length of the options array being greater than 0 when the data is retrieved
<modal-component *ngIf="options.length > 0"></modal-component>
  1. Implement a function that utilizes the router to redirect the user upon clicking an option within the modal
html
<div (click)="redirect(value)">option 1</div>

ts
redirect(value) {
    this.router.navigate([value]);
}

This method presents a direct and effective solution to the problem at hand.

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

What is the best way to conceal an image tag depending on an ajax response?

What is the correct jQuery statement to replace the "//Needed incantation" comments below so that the image tags are displayed or hidden based on the AJAX responses? <!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN" "http://www.w3.org/TR ...

Adding Angular to your current project involves integrating the framework into your

Previously, the task of initializing a project was done using ng init, but that command no longer exists. Another option is to run ng new from a higher level in the directory structure and specify the folder for your existing project. However, this will ...

What is the best way to import scss files and images in Vue.js storybook stories?

In this component, I am importing an image using src="@/assets/images/logo.png" with the help of @ for addressing: <template> <div class="loading_container"> <img class="loading_logo" src="@/assets/ ...

Selecting elements dynamically with JQuery

Trying to use a jQuery selector within plugin code created by a 3rd party has proved difficult. When hardcoded, it works fine; however, when attempting to use variables for dynamic selection, the object cannot be found. The lack of id handles in the CSS c ...

What is the best way to create TypeScript declarations for both commonjs modules and global variables?

Wanting to make my TypeScript project compatible with both the commonjs module system and globals without modules. I'm considering using webpack for bundling and publishing it into the global namespace, but running into issues with the definitions (.d ...

"Uh-oh! Encountered a new unexpected runtime error. Can't seem

While working on my portfolio in Next.js, I encountered an issue. I added a header to display on all pages by placing it in _app.js without making any changes to _document.js. Here is the error message: Unhandled Runtime Error Error: No router instance fo ...

Strategies for sorting data in d3js/dimplejs visualizations

I am looking to enhance the interactivity and responsiveness of a d3js/dimplejs chart by implementing filtering based on clicks in the legends for different series. The code I tried below did not hide the series as expected, although it worked well with a ...

transmit information and documents to server using Axios

I am working on a project using ReactJs and I need to send data to a Laravel API using Axios. Here is the code snippet I have tried: export const send = (data, File) => { const formData = new FormData(); formData.append('media', File); ...

Is there a way to automatically update the input value when I refresh the page following a click event?

Currently, I have multiple p elements that trigger the redo function upon being clicked. When a p element is clicked, the quest[q] template is loaded onto the .form div. These templates are essentially previously submitted forms that sent values to an obj ...

Best method for integrating widget scripts in Angular 5

I am in the process of developing an Angular 5 application and I have encountered a challenge while trying to integrate a widget into one of the components. Following the guidance provided in this particular question, I attempted to add the widget as inst ...

When using node.js express, the req.body object does not yield any results

My web server setup with express appears to be functioning correctly, however, I am encountering an issue where the req.body returns nothing. Despite not receiving any errors, the console.log(req.body) doesn't show anything in the console output. Stra ...

Using Vue.js, you can set a method to return data directly to

Having just begun my journey with Vue, I find myself in a bit of a predicament. As part of my learning process, I am developing an app for tracking episodes in TV series. The initial step involves searching for series and adding them to a database. When co ...

When attempting to add images to a column using JsPDF Autotable, I encountered an error stating that the property 'getElementsByTagName' is not recognized

I'm attempting to include an image from an AWS S3 bucket using its URL. The data is structured in the following format: [{ netValue: 13702.5, prodCode: "UPP", prodDesc: "Privacy Panel", prodImg: "https://url/images/UPP ...

The function window.open when using the target '_blank' will launch a fresh browser window

I'm attempting to open a link in a new browser tab (not a new window). When I place a link on the page like this: <a href="#" onclick="window.open('http://www.google.com', '_blank');"> Click Here</a> when a user clic ...

The code in check.js causes a square of dots to emerge on the screen in Skype

Trying to add a Skype call button to my page has been successful, but there's one issue - a pesky white dot keeps appearing at the bottom of the footer. The script source I used is as follows: <script src="http://download.skype.com/share/skypebu ...

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 ...

Using the Backbone.js library to make secure requests over https

Currently, I am developing a single page application that involves using Backbone.js and Marionette on the front end, combined with Django and Tastypie on the back end. Recently, I successfully implemented an SSL certificate on the web server and configure ...

What is the best way to create a grid item that maximizes the available space in ReactJS Material-UI?

Currently, I am working with the material-ui grid system in a reactjs project. In a column grid container layout, my goal is to display two grid items and have a third item fill up the remaining space between them. Essentially, I want it to look like this: ...

Storing new li and ul elements to the database by utilizing an array for dynamic saving

I am in the process of creating a user interface for an application where users can add unordered lists and list items by clicking on "add question". I have successfully designed the front end part and here is a preview: However, I am facing difficulties ...

What is the best way to transform a date such as '2021-06-01T11:17:00-07:00' into the corresponding date and time for a specific location (New Delhi) using JavaScript?

How can I convert a date in the format '2021-06-01T11:17:00-07:00' to ISO standard? What does this format represent? let date = new Date('2021-06-01T11:17:00-07:00'); console.log(date.getHours() + " :" + date.getMinutes()); I ...