Angular 16 HttpClient post request with asynchronous action

Here I am working on integrating JWT in Angular with a .Net Core API. When I start my Angular server and launch the application, I encounter the following scenarios:

  • Attempting with correct credentials initially fails, but retrying allows it to work.
  • Trying with incorrect credentials at first fails, then retrying again causes failure again.
  • Starting with correct credentials fails, trying again with incorrect ones leads to successful login.

I want to clarify that the success of logging in depends on the JWT token already being stored in localStorage as a value. This means that at a certain point, the API call works; it's just not validated correctly at the right time for some reason.

To dig deeper into this issue, here is the relevant code snippet:

This is the AuthService component:

import { HttpClient, HttpHeaders } from '@angular/common/http';
import { Injectable } from '@angular/core';
import { User } from 'src/app/models/User';


const httpOptions = {
  headers: new HttpHeaders({
    'Content-Type':'application/json;charset=UTF-8'
  })
}


@Injectable({
  providedIn: 'root'
})
export class AuthService{

  public loggedIn = false;

  constructor(private httpClient:HttpClient) { }

  login(user: User){
    // Set the User object with the corresponding credentials as the request body
    const body = JSON.stringify(user);
    // Send the post request with its corresponding headers and body
    this.httpClient.post<any>("https://localhost:7054/login", body, httpOptions)
    .subscribe(data => {
      // Here, the data received from the API is known (as I developed it myself):
      // it will only contain the token if successful, and will return a bad request
      // with a blank token if it fails, so the only scenario where the token may
      // be valid is through a HTTP 200 response.
      localStorage.setItem("token", data.token);
    });
  }
}

And this is the HomeComponent.ts file:

import { Component } from '@angular/core';
import { Router } from '@angular/router';
import { AuthResponse } from 'src/app/models/AuthResponse';
import { User } from 'src/app/models/User';
import Swal from "sweetalert2";
import { AuthService } from 'src/app/services/auth/auth.service';

@Component({
  selector: 'app-home',
  templateUrl: './home.component.html',
  styleUrls: ['./home.component.css']
})

export class HomeComponent {
  user: User = new User();

  constructor(private router: Router, private authService:AuthService) {
  }

  token: AuthResponse = new AuthResponse();

  //Login method
  login(username: string, password: string) {

    // - User authentication
    // The verification occurs in the backend for security purposes.

    // Validate credentials are not blank
    if(username != "" || password != ""){

      // Create a new User object, which will be passed to the API as request body
      let user = new User();
      user.email = username;
      user.pass = password;

      // Call the auth service
      this.authService.login(user);

      // If the previous operation went well, the token must be stored, and we should be able to log in
      if(localStorage.getItem("token")){
        this.router.navigate(['/home']);
      }
      // If the request returns nothing, the credentials are incorrect, therefore an error alert will be sent, plus the token is set to blank
      else{
        this.sendError("Credentials are incorrect.")
        localStorage.setItem("token", "");
      }
    }
    else{
      // If the credentials are blank, therefore an error alert will be sent, plus the token is set to blank
      this.sendError("Please type your credentials.")
      localStorage.setItem("token", "");
    }
  }

  sendError(error:string){
    Swal.fire({
      icon: 'error',
      title: 'Error',
      text: error,
    })

  }
}

If someone could offer insight into why this unexpected behavior is occurring, I would greatly appreciate it. There seems to be something missing or incorrect in my implementation, but I can't pinpoint it.

Thank you for any assistance provided.

I've attempted using interceptors to handle the response but they haven't resolved the issue related to the strange behavior.

I've also tried enforcing token validation, but it doesn't seem to have an impact since the token appears to get cached between attempts.

Answer №1

There are multiple issues that need attention.

Starting with this section:

  if(localStorage.getItem("token")){
    this.router.navigate(['/home']);
  }
  // If the request returns nothing, the credentials are incorrect, therefore an error alert will be sent, plus the token is set to blank
  else{
    this.sendError("Credentials are incorrect.")
    localStorage.setItem("token", "");
  }

The problem lies in the else statement where the token is being set. This results in the app redirecting to the home route every time 'login' is clicked, regardless of successful login status.

Moreover, the code for login is asynchronous while the check is synchronous. This means the token check could occur before the login function finishes execution.

 // Call the auth service
  this.authService.login(user);
  
  // If the previous operation went well, the token must be stored, and we should be able to log in
  if(localStorage.getItem("token")){

A potential solution could involve having the authService return an Observable and processing the token using a pipe operator instead of subscribe() method.

login(user: User){
    const body = JSON.stringify(user);
    return this.httpClient.post<any>("https://localhost:7054/login", body, httpOptions)
    .pipe(tap(data => {
      localStorage.setItem("token", data.token);
    }));
  }

You can enhance this by subscribing to the Observable in your component code to ensure proper handling after login() completes:

  // Call the auth service
  this.authService.login(user).subscribe(() ==> {
     if(localStorage.getItem("token")){
       this.router.navigate(['/home']);
     } else{
       this.sendError("Credentials are incorrect.")
       localStorage.setItem("token", "");
     }

  });
  

It's advisable to handle errors based on server-received error codes rather than token existence checks.

Note: The code shared is hypothetical and untested but offers an improved approach to address the identified issues.

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

Access Select without needing to click on the child component

I am curious to learn how to open a Select from blueprint without relying on the click method of the child component used for rendering the select. <UserSelect items={allUsers} popoverProps={{ minimal: false }} noResults={<MenuItem disabled={ ...

Leverage the capabilities of one service within another service

I have been working on enhancing the functionality of Http so that when a user encounters a 403 error, their user information is removed and they are redirected to the login page. I have shared an example of AuthHttp below for reference. @Injectable() ...

Experiencing code coverage challenges in Angular 8 relating to function properties

Looking for suggestions on how to create a unit test case in Jasmine to address the code coverage problem. Any ideas? ...

The Angular Date Pipe is currently unable to display solely the Month and Year; it instead presents the complete date, including day, month,

I'm currently using the Bootstrap input date feature to save a new Date from a dialog box. However, I only want to display the month and year when showing the date. Even though I added a pipe to show just the month and year, it still displays the mont ...

Angular can display text on hover based on the state shown in a <td> element

Working on an Angular 7 project, I have a <td> element where I display different colors to indicate the status of a task: Red - Indicates 'Delayed' Orange - Indicates 'In progress' Grey - Indicates 'Rejected' Cu ...

Using TypeScript gives you the ability to specify the type of an object while destructuring it,

Currently in the process of refactoring a NodeJS application to TypeScript. I have been consistently using object destructuring and have also been creating aliases while object destructuring, as shown in the code block below. My question is, how can I sp ...

What is the process of declaring a variable within a class in TypeScript?

When setting up an instance variable inside my Angular component like this: @Component({ selector: 'app-root', templateUrl: './app.component.html', //template: `` styleUrls: ['./app.component.css'] }) export class AppCo ...

Encounter a net::ERR_EMPTY_RESPONSE error while trying to deploy an Angular application on a production server

I have successfully developed an Angular App on my local machine and now I am facing challenges while trying to deploy it on a Windows production server. I have set up Apache to serve the App along with the Rest Service API. Accessing the App through the ...

Angular 12: How to detect when a browser tab is closing and implement a confirmation dialog with MatDialog

I have a scenario where I am checking if the browser tab is closed using the code below. It currently works with windows dialog, but I would like to incorporate MatDialog for confirmation instead. @HostListener('window:beforeunload', ['$eve ...

Testing my node.js application with Postman

Currently, I'm utilizing the Postman extension on Chrome to test out my Node.js (express module) application. The goal is for the program to accept user input through Postman and retrieve specific information from within the program based on that inpu ...

Passing a method from a component to a service in Angular 9

Recently, I've been working on some websocket code that involves sending a message to the server and receiving a reply. The current implementation is functional, but I'm looking to refactor it by encapsulating it within a service and then callin ...

Attempting to modify PHP query following submission of data via Axios in Vue application

Fetching a JSON object through a PHP query from a service known as BullHorn can be done like this: <?php echo getBhQuery('search','JobOrder','isOpen:true','id,title,categories,dateAdded,externalCategoryID,employmentTy ...

Using TypeScript and NestJs: Spread types can only be generated from object types

I'm encountering an issue while trying to pass two parameters using the spread operator from the book.controller to the book.service.ts service. The error message I'm receiving is: Spread types may only be created from object types It's w ...

Is there a way for me to retrieve the header values of a table when I click on a cell?

I have a project where I am developing an application for booking rooms using Angular 2. One of the requirements is to be able to select a cell in a table and retrieve the values of the vertical and horizontal headers, such as "Room 1" and "9:00". The data ...

Customizing content-type header in Angular httpclient

I need help sending a block of ndjson to an API using Angular httpClient. The API requires each JSON object to be newline delineated, rather than accepting an array of objects. This means I have to send a string of JSON objects with newlines between them. ...

What is the recommended default value for a file in useState when working with React and TypeScript?

Can anyone help me with initializing a file using useState in React Typescript? const [images, setImages] = useState<File>(); const [formData, setFormData] = useState({ image: File }); I'm facing an issue where the file is sho ...

Exploring the JSON Array in Angular5 with the power of ngFor

Currently, I am working on a project using Angular5 and encountering an issue with the *ngFor directive. The model class I have defined looks like this: export class FetchApi { value: Array<String>; api_status: string; api_version: string; d ...

In configuring the print settings, I specified margins to ensure proper formatting. However, I noticed that the margin adjustment only applies to the first page. I need

I have a method that retrieves margin top value from the backend. It works perfectly on the first page of print, but on the second page, the margin top space is missing. initializePrintingSettings() { this.printService.fetchPrintSettings().subscribe(respon ...

Identifying imports from a barrel file (index.ts) using code analysis

Can anyone help me understand how the Typescript compiler works? I am trying to write a script that will parse each typescript file, search for import declarations, and if an import declaration is using a barrel-file script, it should display a message. Af ...

Display the current date in YYYY/MM/DD format using a single method in React and TypeScript

Is there a better way to retrieve YYYY/MM/DD data using just one method? I attempted the following: date = created_at // from API const sendDate = `${String((date.getMonth() + 1)).padStart(2, '0')}${String(date.getDate()).padStart(2, '0&apos ...