Implementing Angular2 routing within an HTTP interceptor

Using Angular 2.4.8, I communicate with the backend via REST and need to include X-Auth-Token in the header of each request. The token is stored in the session, and if it becomes outdated, the server returns a 401 status requiring the application to redirect to the login page.

To handle this, I implemented an HTTP interceptor in my project:

@Injectable()
export class HttpInterceptor extends Http {

    constructor(backend: XHRBackend
        , defaultOptions: RequestOptions
        , private router: Router
    ) {
        super(backend, defaultOptions);
    }

    request(url: string | Request, options?: RequestOptionsArgs): Observable<Response> {
        return super.request(url, options).catch((error: Response) => {
            if ((error.status === 401 || error.status === 403) && 
            (window.location.href.match(/\?/g) || []).length < 2) {
                console.log('The authentication session expires.');
                window.sessionStorage.removeItem('auth-token');
                window.location.href = window.location.href + '/login';
                return Observable.empty();
            }
            return Observable.throw(error);
        });
    }
}

This implementation works well but when using plain redirects instead of the router, the entire application reloads. To resolve this discrepancy, I modified the code:

// window.location.href = window.location.href + '/login';
this.router.navigate(['/login']);

However, the application fails to follow the link. How can I ensure the router navigates correctly?

Edit January 22, 2018

In my app-routing.module.ts:

const routes: Routes = [
    {
        path: 'login',
        component: LoginComponent,
        resolve: {
            boolean: InitResolverService
        }
    },
    {
        path: '**',
        redirectTo: 'system'
    }
];

@NgModule({
    imports: [
        RouterModule.forRoot(
            routes
            // , { enableTracing: true } // <-- debugging purposes only
        )
    ],
    exports: [
        RouterModule
    ]
})
export class AppRoutingModule { }

The InitResolverService includes logic for initial navigation and emits true upon completion of the stream.

Additionally, in the LoginComponent:

@Component({
    selector: 'app-login',
    templateUrl: 'login.component.html',
    styleUrls: ['login.component.less']
})
export class LoginComponent implements OnInit {
    private username: FormControl;
    private password: FormControl;
    public form: FormGroup;
    public displayDialog = false;
    isLoginButtonEnabled = true;
    isResetButtonVisible = false;

    constructor(
        private authService: AuthenticationService,
        private router: Router,
        private route: ActivatedRoute,
        private initService: InitResolverService
    ) {
        this.username = new FormControl(Validators.required);
        this.password = new FormControl(Validators.required);
        this.form = new FormGroup({
            Username: this.username,
            Password: this.password
        });
        this.form.setValue({
            Username: '',
            Password: ''
        });
        this.displayDialog = true;
    }

    ngOnInit() {
        this.initService.showSplash();
        this.authService.canActivate(this.route.snapshot, this.router.routerState.snapshot).subscribe(x => {
            if (x) {
                this.router.navigate(['/']);
            }
        });
    }
}

Answer №1

Our team successfully resolved this issue by developing a custom HTTP service to handle all requests through REST.

Utilizing the custom HTTP service allows you to:

  • specify a central API path
  • generate headers with tokens for authorization
  • manage all HTTP error responses, including 401 errors

Here is a simple code snippet as an example:

import { Injectable } from '@angular/core';
import { Http, Response, Headers } from '@angular/http';
import { Observable } from 'rxjs/Observable';
import 'rxjs/add/operator/map';
import 'rxjs/add/operator/catch';
import { ErrorObservable } from 'rxjs/observable/ErrorObservable';

export const API_PATH = "http://apipath"

@Injectable()
export class CustomHttpService {

    constructor(
        private http: Http,
        public router: Router) { }

    headerWithToken(): Headers {
        const headers = new Headers();
        headers.set('Authorization', 'bearer ' + localStorage.getItem('TOKEN'));
        headers.set('Content-Type', 'application/json');
        return headers;
    }

    get(params: URLSearchParams, method: string): Observable<any> {
        const url = `${API_PATH}/${method}`;
        return this.http.get(url, {params: params, headers: this.headerWithToken()})
        .map(
            res => <Array<any>>res.json()
        )
        .catch(err => {
            const result = this.handleErrors(err, this);
            return result;
        });
    }

    // put and post methods follow the same pattern

    public handleErrors(error: Response, obj: any): ErrorObservable {
        const errData = error.json();
        if (error.status === 401) {
            obj.router.navigate(['/login']);
        } else if (errData.message) {
            // display a message or handle it accordingly
        } else {
            console.log(errData);
        }
        return Observable.throw(error.json());
    }

}

Answer №2

Instead of utilizing an interceptor, I have implemented a method to handle errors for each API call. Here is the code snippet:

// defining a separate error handler method for reusability
private handleError(error: HttpErrorResponse | any) {
  console.error('ApiService::handleError', error);
  if (error.status === 401) {
    this.oAuthService.logOut();
  }
 return Observable.throw(error);
}

I hope this explanation clarifies how I manage errors in my API calls.

Answer №3

Check out this effective code snippet:

@Injectable()
export class ExampleInterceptor implements HttpInterceptor {

  constructor( public auth: AuthenticationService,
               private router: Router ) {
  } 

  public intercept( request: HttpRequest<any>, next: HttpHandler ): Observable<HttpEvent<any>> {
    let url: string = request.url;
    let method: string = request.method;
    console.log(`ExampleInterceptor url=${url},   method=${method}`);

    return next.handle( request ).do( ( event: HttpEvent<any>> ) => {
        console.log(`Successful response from server for request=${request.urlWithParams}`);
    })
    .catch((responseError: any) => {

      if (responseError instanceof HttpErrorResponse) {
        console.error('Response in catch block: ', responseError);

          if ( responseError.status === 401 ) {
            let errorMsg: string = '';

            if ( responseError.statusText === 'Invalid credentials' ) {
              errorMsg = 'Username or password is incorrect';
            }

            // Redirect to login route
            this.router.navigate(['/login'], {queryParams: {msg: errorMsg}});
            return empty();
          }

        return throwError(responseError);
      }


      let error = new HttpErrorResponse({
        status: 500,
        statusText: 'Unknown Error',
        error: {
          message: 'Unknown Error'
        }
      });
      return throwError( error );

    }) as any;
  }
}

Answer №4

To ensure proper functionality in your app.module.ts, make sure to include the following code:

{provide : Http, useFactory: (xhrBackend: XHRBackend, requestOptions: RequestOptions, router: Router) => new HttpInterceptor(xhrBackend, requestOptions, router),deps: [XHRBackend, RequestOptions, Router]}

Within your HttpInterceptor file, implement the following:

 constructor(backend: ConnectionBackend, defaultOptions: RequestOptions, private _router: Router) {
    super(backend, defaultOptions);
  }

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

Creating personalized error messages for string validation in mongoose

Trying to validate a gender field that can only be input as male or female led me to use the mongoose "enum" validator for strings. However, I encountered difficulty in specifying a custom error message for it. My attempt involved wrapping the enum option ...

Using formControl within an *ngIf directive or configuring the minimum date for a datepicker

I'm uncertain about utilizing the formcontrol value to manage the display of another element without using ngModel. I have two different elements that should be impacted by the value of a separate form item. Here's a scenario: <mat-form-field ...

Misunderstanding between Typescript and ElasticSearch Node Client

Working with: NodeJS v16.16.0 "@elastic/elasticsearch": "8.7.0", I am tasked with creating a function that can handle various bulk operations in NodeJS using Elasticsearch. The main objective is to ensure that the input for this funct ...

Tips on monitoring changes in the firebase login status from a different component?

I am currently working on developing a navbar component. Within the navbar, there are two locations for user login. Users can either sign in directly within the navbar itself or utilize another component named settingMenu to handle their login. The settin ...

Tips for linking a column value in a data table with Angular 7

I have an application where I retrieve data in a table format. This front end of this application is built on angular7. Now, I need certain column values to be links that when clicked will display a new component. For example: Column1 Column2 ...

Implementing Angular's Advanced Filtering Across Multiple Data Fields

I am looking to create a custom filter for a list. Here is an example of the Array of Objects: myList: [ { "id": 1, "title":"title", "city":"city name", "types":[ { ...

What are the appropriate token classifications for Dependency Injection (DI)?

Back in the days of Angular 1, providers only accepted strings as tokens. However, with the introduction of Angular 2, it seems that class tokens are now being predominantly used in examples. Take for instance: class Car {} var injector = ResolveInjector ...

"Exploring the dynamic duo: Algolia integration with Angular

I've been following a tutorial on implementing instantsearchjs in my website, which can be found here. Everything is set up correctly and I can query for results in .JSON format from my website. However, I'm having trouble figuring out how to r ...

Using memoization for React Typescript callback passed as a prop

My component is designed to display data retrieved from a callback provided in the props. To prevent an infinite loop caused by mistakenly passing anonymous functions, I am looking for a method to enforce the usage of memoized callbacks. const DataRenderer ...

What is the method for storing the values of a Select tag from react-select into a state?

I am currently facing an issue with setting values that I receive when selecting multiple items from a tag using "react-select". The state outcome I am looking for when selecting values from the tag should be: ['user._id', 'user._id', & ...

Why are my values not being applied to the model class in Angular 7?

I'm currently developing an online shopping website where I have defined my order Model class as shown below: import { User } from './user.model'; export class Order { constructor(){} amount: Number = 0; status: String = ""; date: ...

Having trouble importing createSelector from @ngrx/store

When trying to import @ngrx/store, I encountered an error stating that the module '".../node_modules/@ngrx/store/index"' has no exported member 'createSelector'. This issue arose while using @ngrx/[email protected]. I would great ...

Can the contents of a JSON file be uploaded using a file upload feature in Angular 6 and read without the need to communicate with an API?

Looking to upload a JSON file via file upload in Angular (using version 6) and read its contents directly within the app, without sending it to an API first. Have been searching for ways to achieve this without success, as most results are geared towards ...

Looking for the most effective approach to implement responsive styling in an Angular 7 project? I've already included CSS for every component, but I

Looking for guidance on implementing Responsive Style in an Angular 7 Project. I have included CSS for each component, but when I apply responsive styles globally, they do not inherit as expected. Should I add responsive styles individually to each compo ...

Angular generates a dynamic interface to fetch data from Wordpress REST API posts (special characters in property names are causing issues)

I've been developing a front-end Angular application that interacts with the Wordpress REST API to fetch and display post data. My goal is to create an interface to handle the responses and render the posts in the template. However, I encountered an ...

The functionality of ngModel seems to be malfunctioning when used within select options that are generated inside

I'm currently working on dynamically adding options to an HTML select element within a for loop. I am using [(ngModel)] to get the selected option, but initially no option is pre-selected. Here's a snippet of the code: <table align="center"& ...

What is the best way to trigger a function in React when a constant value is updated?

In my React application, I have 3 components. The parent component and two child components named EziSchedule and EziTransaction. Each component fetches its own data from an API. The data to display in the EziTransaction child component depends on the reco ...

Retrieving a specific data point from the web address

What is the most efficient way to retrieve values from the window.location.href? For instance, consider this sample URL: http://localhost:3000/brand/1/brandCategory/3. The structure of the route remains consistent, with only the numbers varying based on u ...

Transfer only designated attributes to object (TS/JS)

Is it feasible to create a custom copy function similar to Object.assign(...) that will only copy specific properties to the target? The code snippet I have is as follows: class A { foo?: string; constructor(p: any) { Object.assign(this, p ...

Sharing Angular 4 components in my project - collaboration at its finest

I have been working on an Angular project that consists of a variety of UI components. I would like to make these components available for use in other Angular projects and share them within the npm community. All of the components are located in a shared ...