Displaying a loading spinner while making HTTP requests in Angular 7

I want to implement a spinner for my Angular 7 application's HTTP requests. To achieve this, I have created an HttpInterceptor class, a loader service, a loader component, and a Subject to manage the state of the spinner (whether it should be shown or hidden). However, I am facing an issue with the communication between the loader service and the loader component.

The HttpInterceptor appears to be functioning correctly as it is outputting true and false states to the console (see code snippet below). However, there seems to be a discrepancy in the implementation of the loader service and loader component. I have used an ngIf directive to toggle the visibility of the spinner based on the state of the Subject.

Here is the LoaderService:

import { Injectable } from '@angular/core';
import { Subject } from 'rxjs';

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

  isLoading = new Subject<boolean>();

  show() {
    console.log('true');
    this.isLoading.next(true);
  }

  hide() {
    console.log('false');
    this.isLoading.next(false);
  }

  constructor() { }
}

And here is the LoaderComponent:

import { Component, OnInit } from '@angular/core';
import {LoaderService} from '../loader.service';
import { Subject } from 'rxjs';

@Component({
  selector: 'app-loader',
  templateUrl: './loader.component.html',
  styleUrls: ['./loader.component.css']
})
export class LoaderComponent implements OnInit {

  isLoading: Subject<boolean> = this.loaderService.isLoading;

  constructor(private loaderService: LoaderService) { }

  ngOnInit() {
  }

}
<div *ngIf="isLoading | async" class="d-flex justify-content-center">
  <div class="spinner-border" role="status">
    <span class="sr-only">Loading...</span>
  </div>
</div>

Currently, the spinner is not displaying or hiding properly. The console only shows 'true' when the loading starts and 'false' when it ends. I have included the loader selector in the app.component.html file, so everything should be set up correctly. Can you spot any mistakes in my implementation?

Answer №1

Developing an HttpInterceptor that can indicate the state of HTTP requests with a boolean value is quite challenging. Managing multiple concurrent requests happening simultaneously requires emitting true/false signals appropriately, not just for each individual HttpRequest. It is crucial to emit true only once when a request begins and then emit false after the last request completes.

An effective approach is to monitor the number of ongoing requests concurrently.

Below is the implementation of the interceptor I utilize. It emits true only on the initiation of a new request and false when all requests are completed.

import {HttpEvent, HttpHandler, HttpInterceptor, HttpRequest} from '@angular/common/http';
import {Injectable} from '@angular/core';
import {Observable, Subject} from 'rxjs';
import {distinctUntilChanged, finalize, map} from 'rxjs/operators';

class BusySubject extends Subject<number> {
    private _previous: number = 0;

    public next(value: number) {
        this._previous += value;
        if (this._previous < 0) {
            throw new Error('unexpected negative value');
        }
        return super.next(this._previous);
    }
}

@Injectable()
export class BusyInterceptor implements HttpInterceptor {

    private _requests: BusySubject = new BusySubject();

    public get busy(): Observable<boolean> {
        return this._requests.pipe(
            map((requests: number) => Boolean(requests)),
            distinctUntilChanged(),
        );
    }

    public intercept(req: HttpRequest<any>, next: HttpHandler): Observable<HttpEvent<any>> {
        this._requests.next(1);
        return next.handle(req).pipe(finalize(() => this._requests.next(-1)));
    }
}

p.s. This could properly be done using a scan() operator, but I haven't had time to update the code.

Answer №2

It is recommended to utilize a BehaviourSubject in place of a Subject. Unlike a Subject, which only emits values when next() is called, a BehaviourSubject remembers the last emitted value. This ensures that even if the component initializes after the service has pushed a value, it will still receive the latest data.

By using a BehaviourSubject, your template can subscribe and access the most recent value for comparison purposes.

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 cascading select fields in Angular using form controls

Encountering a peculiar issue with Angular Reactive Forms and a dependent drop-down list, undoubtedly stemming from my lack of expertise. I aim to establish the options in the second drop-down reliant on the selection in the first drop-down. Upon changing ...

Delete a particular instance of a component from an array within the parent component when a button is clicked within the child component, depending on a specific condition in Angular

One scenario I am facing involves the removal of a component instance from an array (located in the parent component) when a button is clicked inside the child component, based on a specific condition. https://i.sstatic.net/YPFHx.png Within the parent co ...

Retrieving the previous and current URL in Angular 8

Need help setting variables prevUrl and currentUrl in Angular 8 by fetching previous and current URLs. The scenario involves two components - StudentComponent and HelloComponent. When transitioning from HelloComponent to StudentComponent, I face an issue. ...

Modifying the Design of Angular Material Dialog

I am currently working on creating a modal using Angular Material Dialog, but I want to customize the style of Angular Material with Bootstrap modal style. I have tried using 'panelClass' but the style is not being applied. Has anyone else faced ...

Link checkboxes to a value rather than using true/false with reactive forms

Looking for help with reactive/model-driven forms to bind an array of checkboxes to a list of values, rather than boolean values. Here's the issue demonstrated in this Plunker: http://plnkr.co/edit/a9OdMAq2YIwQFo7gixbj?p=preview. Currently, the value ...

Using generic arrow functions for curry operations in TypeScript can lead to type errors

This particular function is designed to split a string into three separate parts. transform<T extends String, N>(arr: T): T { let length = arr.length; const split = (fn: (i: N) => T) => (p: (q: N) => N) => (arg: N) => fn(p(arg)); cons ...

Is node.js necessary for running TypeScript?

Node.js is necessary for installing TypeScript. I believe that TypeScript utilizes Node.js to compile .ts files into .js files. My concern is whether the generated .js file needs node.js in order to function properly. From what I've observed, it seem ...

Hiding columns in TanStack Ver 8: A step-by-step guide

Can someone assist me with dynamically hiding specific columns in Tanstack table ver 8? I have a column defined as follows: { header: "Case Number", accessorKey: "caseNumber", visible: false, // using this value ...

Final Page Index (Backend pagination with Ng2-Smart-Table)

Currently, I am utilizing the Ng2-Smart-Table component along with server-side pagination. Upon testing out the provided example, I delved into the requests being made and noticed that only JSON records or objects were being returned. Here is an example of ...

Creating Typescript libraries with bidirectional peer dependencies: A complete guide

One of my libraries is responsible for handling requests, while the other takes care of logging. Both libraries need configuration input from the client, and they are always used together. The request library makes calls to the logging library in various ...

Developing an S3 Bucket policy with Pulumi

I am currently working on setting up an S3 bucket and a corresponding S3 policy using Pulumi with TypeScript. However, during the pipeline execution, I encountered the following error in the test stage: expect(received).toEqual(expected) // deep equality - ...

Retrieve JSON information from an API using Angular 2

I am facing an issue with retrieving JSON data from an API that I created. Despite using Angular code to fetch the data, it seems to be unsuccessful. Here is the code snippet: getBook(id: string){ return this._http.get(this.url + 'books/' + ...

What steps can be taken to avoid an abundance of JS event handlers in React?

Issue A problem arises when an application needs to determine the inner size of the window. The recommended React pattern involves registering an event listener using a one-time effect hook. Despite appearing to add the event listener only once, multiple ...

utilize undefined files are assigned (Typescript, Express, Multer)

I am facing an issue while trying to save image uploads to a folder named "/images". The problem lies in the fact that req.files is appearing as undefined for some reason. Below is the relevant code snippet. Feel free to ask any questions, any assistance w ...

Alert: Using Angularfire SDK could result in console log issues

I've been encountering this persistent warning that I just can't seem to get rid of no matter what I try. Here are the versions I'm using: angular 11.0.1 @angular/fire 6.1.3 firebase 7.0.0 || 8.0.0 https://i.sstatic.net/5Tyt5.png ...

What is preventing MenuItemLink from being displayed in the menu?

I have created a unique page for users to purchase subscriptions, but I am having trouble accessing that page because the button is not appearing in the menu. I followed the steps outlined in the official guide, but only the dashboard and resources buttons ...

What is the best way to add all IDs to an array, except for the very first one

Is there a way to push all response IDs into the idList array, excluding the first ID? Currently, the code below pushes all IDs to the list. How can it be modified to exclude the first ID? const getAllId = async () => { let res = await axios({ m ...

Unable to compile Angular 5 using the AOT systemjs configuration

I've hit a roadblock in finding a solution to my issue. Maybe someone out there can lend me a hand. I'm in the process of upgrading from ng 4.4.4 to 5.0.1 and everything seems to be functioning fine in JIT mode. However, when attempting to compi ...

Issue encountered: NullInjectorError - R3InjectorError has been caused by a problem within AppModule regarding InjectionToken HTTP_INTERCEPTORS linking to TransferState

Error Image:- Error Images I am encountering this error: ERROR NullInjectorError: R3InjectorError(AppModule)[InjectionToken HTTP_INTERCEPTORS -> [object Object] -> TransferState -> TransferState -> TransferState]: NullInjectorError: No provider ...

What are some methods for integrating a database-driven approach to Angular's internationalization features?

After exploring the Angular i8n guide found here: https://angular.io/guide/i18n, I have gained a good understanding of the concepts. While I appreciate the use of markup and hints in the file, I find it challenging that text resources are confined to a pe ...