Tips on detecting an error in a Request, launching a modal, and attempting again once the modal is closed using RxJS

My goal is to implement a call to a server using Angular2's HTTP class that can handle authorization failures (401).

The process should follow these steps:

  • The user initiates a request to the server with myService.getSomething().subscribe()
  • If the server responds with a 401 error, prompt a modal window asking for user credentials.
  • User successfully logs back into the application
  • The modal closes and triggers a callback
  • The callback retries the initial request (myService.getSomething().subscribe())

This is what I have currently:

export class MyService {
    // ...
    public getSomething(): Observable<Response> {
        return this.http.get(url, options).catch((res: any, ob: any) => this.errorHandler(res, ob));
    }

    public errorHandler(res: Response, ob: Observable<any>): Observable<Response> {
        if (res.status === 401) {
            this.modalService.open(new ModalConfig({
                content: LoginModalComponent,
                close: () => { ob.retry(1); console.log("weow") } 
            }));
        }
        else {
            return Observable.throw(res.json());
        }
    }
}

doSomething() usage:

doSomething().map((r) => r.json()).subscribe((r) => ....)

Update 1

I revised my code based on @Thierry Templier's solution.

private errorHandler(res: Response, ob: Observable<any>): Observable<Response> {
    if (res.status === 401) {
        let closedSubject = new Subject();
        this.modalService.open(new ModalConfig({
            content: LoginModalComponent,
            close: () => { closedSubject.next(res);}
        }));
        return ob.retryWhen(() => closedSubject);
    }
    else {
        return Observable.throw(res.json());
    }
}

Unfortunately, it still doesn't function as intended. The retryWhen executes immediately without waiting for closedSubject.next() to be called, resulting in an infinite loop.

Update 2

I created a plunker to demonstrate the infinite loop:

https://plnkr.co/edit/8SzmZlRHvi00OIdA7Bga

Warning: running the plunker will spam your console with the string 'test'

Update 3

After following Thierry's correct answer, I searched for a way to avoid using the protected source field. Upon requesting to make the field public on rxjs's issue tracker, a contributor provided a better solution.

public get(url: string, options?: RequestOptionsArgs): Observable<Response> {
    return super.get(url, options).retryWhen((errors: any) => this.errorHandler(errors));
}
private errorHandler(errors): any {
    return errors.switchMap((err) => {
        if (err.status === 401) {
            let closedSubject = new Subject();
            this.modalService.open(new ModalConfig({
                content: LoginModalComponent,
                close: () => { closedSubject.next(err); }
            }));
            return <any>closedSubject;
        }
        else {
            return Observable.throw(err.json());
        }
    });
}

I opted not to use .catch to avoid utilizing the source field.

Answer №1

It is crucial to always return an observable, even when handling a 401 error:

public errorHandler(res: Response, ob: Observable<any>): Observable<Response> {
    if (res.status === 401) {
        let closedSubject = new Subject();
        this.modalService.open(new ModalConfig({
            content: LoginModalComponent,
            close: () => {
              closedSubject.next();
        }));
        return ob.retryWhen(() => closedSubject);
    }
    else {
        return Observable.throw(res.json());
    }
}

For more information, refer to this article: .

Edit

The issue lies in the second parameter of the catch callback not being the source observable. The source observable should be accessed via its source property:

return ob.source.retryWhen((errors) => closedSubject);

You can see it in action in this plunkr example: https://plnkr.co/edit/eb2UdF9PSMhf4Dau2hqe?p=preview.

Answer №2

Perhaps utilizing the retryWhen operator would be beneficial.

Answer №3

It seems like the solution has been updated slightly, as newer versions of Angular require the use of the pipe() method. As a result, I opted for a custom operator solution. One advantage is that the handleError() method can be exported as a global function and utilized in multiple services.

To delve deeper into this solution, check out:

export class MyService {
// ...
public getSomething(): Observable<Response> {
    return this.http.get(url, options).pipe(this.handleError('You can provide a custom error message here'));
}

private handleError(errorMessage: string) {
    return (source: Observable<any>) => source.pipe(
        retryWhen(errors => errors.pipe(
            mergeMap((errorResponse: HttpErrorResponse) => {
                console.error(errorMessage);
                if (errorResponse.status === 401) {
                    const closedSubject = new Subject();
                    this.modalService.open(new ModalConfig({
                        content: LoginModalComponent,
                        close: () => {
                            closedSubject.next();
                        }
                    }));
                    return closedSubject;
                }
                return throwError(errorResponse);
            })
        ))
    );
}

}

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

Manipulate object position using Aframe and three.js

I'm working on developing a game using A-frame. I'm trying to add a shooting effect by incorporating a gun model that follows the user's cursor. I've coded a click event so that an object moves in front of the gun and follows the direct ...

Issues with type errors in authentication wrapper for getServerSideProps

While working on implementing an auth wrapper for getServerSideProps in Next.js, I encountered some type errors within the hook and on the pages that require it. Below is the code for the wrapper along with the TypeScript error messages. It's importan ...

Is there a way to generate rows and columns dynamically using a combination of Bootstrap and Angular 2?

I am facing the challenge of dynamically inserting content from a component into my DOM using *ngFor, based on the size of an array of contents. My goal is to create a layout where there are 3 elements in a row for lg-12, 2 elements for md/sm, and 1 eleme ...

What is the best way to select specific points from a THREE.Points object?

I am working on a single THREE.Points() representing a point cloud and I am trying to select individual points using mouse clicks. starsGeometry = new THREE.Geometry(); for ( var i = 0; i < 10000; i ++ ) { var star = new THREE.Vector3( ...

angular2 angular-entity directive

I have developed a component that accepts a template: export class TemplateParamComponent implements OnInit { @Input() items: Array<any>; @Input() template: TemplateRef<any>; } Here is the HTML code: <template #defaultTemplate le ...

Guide on configuring Angular validation to trigger on blur events and form submission

Validation in Angular is currently set to update on model change, but this method can be unfriendly for user interface as it displays errors upon keyup. An optimal solution would involve displaying error messages on blur and also on form submission. After ...

Transform a string (variable) into an object using JSON.parse, encountering an unexpected token error

I am struggling with parsing a string variable back to an object. Despite searching through various posts on this issue, I have not found a solution that works for me. if(subMatch.match(/\{.*\}/)){ /// new Object of some sort var o ...

Leveraging pug for creating unformatted text

Can pug be used or configured to produce plain text instead of HTML? I want the code below to output Hello John Doe instead of <Hello>John Doe</Hello> render("Hello #{name}", { name: "John Doe" })) ...

Executing an xajax/ javascript function simultaneously

Is there a way to simultaneously execute the same function? Here is an example: function convert_points() { show_loading(); xajax_ConvertPoints(); xajax_GetRegularGamingCards(); } When xajax_ConvertPoints is called, there seems to be a mill ...

The current version of HTML5 Context Menus is now available

I'm in need of implementing the HTML5 Context Menu feature. Currently, only Firefox supports it. My main objective is to add some menu options without replacing the existing context menu. How can I achieve the same functionality now? I am aware of va ...

Incorporate an image into a Component template by utilizing a URL that is provided as a property of the Component in Vue and Vite

One of the challenges I faced was setting the 'src' attribute of an image in a Component's template from a property value. Initially, I attempted to do this as follows: Gallery View, where the Component is called multiple times <script s ...

Discover the methods for accessing an array of IDs by implementing an event trigger in conjunction with checkboxes

Can you assist me with a dilemma I'm facing? I've developed a static form containing numerous checkboxes and checklists. My challenge arises when trying to integrate a JavaScript code that automatically selects subcategories when parent checkboxe ...

Tips for efficiently executing npm run build without encountering any nuxt build errors

Recently, I encountered a frustrating issue with Nuxt build errors. When attempting to run the command npm run dev, my localhost server operates smoothly without any hiccups. However, upon executing npm run build, a series of errors like the one below surf ...

The function to set the state in React is malfunctioning

I'm currently in the process of developing a website where I utilize fetch to retrieve information and display it to the user. Initially, I opted to save this data in a state variable, but for some reason, it's not functioning as expected. Upon ...

Ensuring the accuracy of user input in an AngularJS form

Attempting to validate user input in a form before sending it to the server, however, encountering an issue where the input field is not being checked and no error is being reported. Implemented a custom validator directive: `var QUERY_REGEXP = /[A-Z,a-z, ...

Passing arguments inside the source attribute of an image or link tag in Node.js and

As a beginner in NodeJS, I am facing an issue with passing arguments inside links and image sources. In my template.html file, I have included various scripts and elements to create a search form. The issue arises when I try to incorporate values from the ...

When dispatching an action through redux, the component replicates itself

After writing code to display a chart and implementing a function to change its timeframe, an unexpected issue arose - a duplicate chart appeared when the function was triggered. Included below is the Chart.js code snippet: let chartProperties = { wid ...

Angular 6 - Patience is key when waiting for an element to load within *ngIf after toggling ngIf to true

I have recently upgraded to angular 6. My HTML code is as follows: <div class="catalog-menus-subnav-wrapper" *ngIf="showMenus"> <div class="hidden-elem"> </div> </div> In this code snippet, the showMenus va ...

Tips for displaying the number of selected values in a select box label (e.g. Choose an Option)

I am currently using the jQuery multiselect API in my application to allow users to select multiple values. However, I am facing an issue where I need to fetch all the selected values when a button next to the multiselect box is clicked. Unfortunately, I h ...

Uploading files with AngularJS without the need for a backend script like PHP, JAVA, or any other server-side language

While this is just a demo and my backend team has not yet started, I would like to save the file to a specific folder within my project (I do not want to save it in the database). All I want is that when I upload a file and click on the upload button, tha ...