Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Angular2: how to redirect if API returns an error?

In my service, I want to describe a behavior when user is being redirected if not authorized.

export class MessagesService {
    constructor (private http: Http) {}

    private _usersUrl = '/users.json';  // URL to web api


    getUsers() {
        return this.http.get(this._usersUrl)
            .map(res => <User[]> res.json().data)
            .catch(this.handleError);
    }

    private handleError (error: Response) {

        if (error.status == 401) {
            // How do I tell Angular to navigate LoginComponent from here?
        } else {
            return Observable.throw(error.json().error || 'Server error');
        }
    }
}

My questions are:

  • Is it even possible?
  • Is that a good practice?
    • If yes, how do I perform that?
    • If no, how else can I do that?
like image 503
Viktor Avatar asked Apr 14 '16 15:04

Viktor


People also ask

How to handle HTTP error in angular?

Whenever the error occurs in an HTTP operation, the Angular wraps it in an httpErrorResponse Object before throwing it back. We catch the httpErrorResponse either in our component class or in the data service class or globally. The Global HTTP error handling is done using the Angular HTTP Interceptor. Suggested Reading: Error Handling in Angular

How do I retry any failed HTTP request with angular interceptor?

As you probably already know this interceptor can be used to modify any incoming or outgoing HTTP requests. How do we get the benefits of an Angular interceptor and use it to retry any failed HTTP request? The answer is simple. To add the retry capability we'll use the retry function that triggers a retry when the Observable fails.

How to get data from the backend in angular?

So usually, in Angular, we create a service, inject the HttpClient and use it to get data from the backend. Nothing fancy here. We inject Angulars HTTPClient and perform a simple get Request. If this request returns an error, we perform some error logic and return an empty observable to tell the caller; An error occurred, but it’s okay, I got this.

What happens when an unauthenticated angular client sends a GET request?

So when an unauthenticated angular client sends a GET request to an endpoint (expecting JSON), it receives the HTML content of the login page, and the JSON parser fails ( Angular2 watch for 302 redirect when fetching resource) const httpObservable = this.http.get (urlThatWillRedirect).subscribe (data => ...) // JSON parser will fail


2 Answers

My approach was to create my own request service and have an interceptor function which wraps the actual request to handle 401 and 403 etc.

Have included it below if you want to have a look at it.

import {Injectable} from "@angular/core"
import {Subscription, Observable} from "rxjs"
import {TokenModel} from "../../models/token.model"
import {TokenService} from "../authentication/token.service"
import {Http, Headers, URLSearchParams, RequestOptions, Request, RequestMethod} from "@angular/http"
import {Router} from "@angular/router"

@Injectable()
export class RequestService
{
    private baseUrl: string;
    private subscription: Subscription;
    private token: TokenModel;

    constructor(public tokenService: TokenService,
                public http: Http,
                public router: Router)
    {
        this.baseUrl = `${process.env.API_URL}/example`;
        this.subscription = this.tokenService.token$.subscribe(token => this.token = token);
    }

    get(path: string, params?: Object, withCredentials?: boolean): Observable<any>
    {
        this.checkAuthorised();

        const url: string = this.baseUrl + path;
        const headers: Headers = new Headers({
            'Accept': 'application/json'
        });

        const searchParams = new URLSearchParams(`user_session=${this.token.token}`);

        for (let param in params) searchParams.set(param, params[param]);

        const options: RequestOptions = new RequestOptions({
            url: url,
            method: RequestMethod.Get,
            headers: headers,
            search: searchParams,
            withCredentials: withCredentials
        });

        const request = new Request(options);

        return this.makeRequest(request);
    }

    post(path: string, body?: Object, params?: Object, useDataProperty?: boolean, withCredentials?: boolean): Observable<any>
    {
        this.checkAuthorised();

        const url: string = this.baseUrl + path;

        const headers: Headers = new Headers({
            'Accept': 'application/json',
            'Content-Type': 'application/json',
        });

        const data = JSON.stringify(useDataProperty ? {data: body} : body);

        const searchParams = new URLSearchParams(`user_session=${this.token.token}`);

        for (let param in params) searchParams.set(param, params[param]);

        const options: RequestOptions = new RequestOptions({
            url: url,
            method: RequestMethod.Post,
            headers: headers,
            body: data,
            search: searchParams,
            withCredentials: withCredentials
        });

        const request = new Request(options);

        return this.makeRequest(request);
    }

    makeRequest(request: Request)
    {
        return this.intercept(this.http.request(request).map(res => res.json()));
    }

    intercept(observable: Observable<any>)
    {
        return observable.catch(err =>
        {

            if (err.status === 401)
            {
                return this.unauthorised();

            } else if (err.status === 403)
            {
                return this.forbidden();
            } else
            {
                return Observable.throw(err);
            }
        });
    }

    unauthorised(): Observable<any>
    {
        this.tokenService.clear();
        this.router.navigate(['/login']);
        return Observable.empty();
    }

    forbidden(): Observable<any>
    {
        this.router.navigate(['/']);
        return Observable.empty();
    }

    checkAuthorised(): void
    {
        if (!this.token.token.length)
        {
            this.router.navigate(['login']);
        }
    }


}
like image 131
rtn Avatar answered Oct 29 '22 01:10

rtn


One direction we've been approaching this on our team is by implementing an API client class. This API client wraps the original Http service.

The idea is that since the Http service produces observables you can easily extend its behavior by adding operators like map, flatMap and catch operators to the original observable produced by the Http service.

I think you will find this example a useful starting point to fix the problem you're having.

import { ApiRequestOptions } from './api-request-options.service';
import { Http, Response, RequestOptions, ResponseContentType } from '@angular/http';

import 'rxjs/add/observable/zip';
import 'rxjs/add/operator/map';
import { Observable } from 'rxjs/Rx';
import { Injectable } from '@angular/core';
import { Router } from '@angular/router';

@Injectable()
export class ApiClient {
    // PLease note, the API request options service is a helper that we introduced
    // to generate absolute URLs based on settings in the client.
    // I did not include it here for brevity.
    constructor(private router: Router, private http: Http, private requestOptions: ApiRequestOptions) {

    }

    get<TResponse>(path: string, queryStringParams?: any): Observable<TResponse> {
        let self = this;

        return Observable.zip(
            this.requestOptions.absoluteUrlFor(path, queryStringParams),
            this.requestOptions.authorizedRequestOptions()
        ).flatMap(requestOpts => {
            let [url, options] = requestOpts;
            return self.http.get(url, options);
        }).catch(response => {
            if (response.status === 401) {
                self.router.navigate(['/login']);
            }

            return response;
        }).map((response: Response) => <TResponse>response.json());
    }

    post<TResponse>(path: string, body: any): Observable<TResponse> {
        let self = this;

        return Observable.zip(
            this.requestOptions.absoluteUrlFor(path),
            this.requestOptions.authorizedRequestOptions()
        ).flatMap(requestOpts => {
            let [url, options] = requestOpts;
            return self.http.post(url, body, options);
        }).catch(response => {
            if (response.status === 401) {
                self.router.navigate(['/login']);
            }

            return response;
        }).map((response: Response) => <TResponse>response.json());
    }

    put<TResponse>(path: string, body: any): Observable<TResponse> {
        let self = this;

        return Observable.zip(
            this.requestOptions.absoluteUrlFor(path),
            this.requestOptions.authorizedRequestOptions()
        ).flatMap(requestOpts => {
            let [url, options] = requestOpts;
            return self.http.put(url, body, options);
        }).catch(response => {
            if (response.status === 401) {
                self.router.navigate(['/login']);
            }

            return response;
        }).map((response: Response) => {
            if (response.status === 200) {
                return <TResponse>response.json();
            } else {
                return null;
            }
        });
    }

    delete(path: string): Observable<Response> {
        let self = this;

        return Observable.zip(
            this.requestOptions.absoluteUrlFor(path),
            this.requestOptions.authorizedRequestOptions()
        ).flatMap(requestOpts => {
            let [url, options] = requestOpts;
            return self.http.delete(url, options);
        }).catch(response => {
            if (response.status === 401) {
                self.router.navigate(['/login']);
            }

            return response;
        });
    }
}
like image 25
Willem Meints Avatar answered Oct 28 '22 23:10

Willem Meints