What is the best way to append to an array if there is no response within 1 second?

I have a feature in my code that monitors requests and responses.

I attempted to display a spinner only if a request takes more than 1 second:

 @Injectable()
export class LoadingInterceptor implements HttpInterceptor {
  private requests: HttpRequest<any>[] = [];

  constructor(private spinnerService: SpinnerService) {}

  intercept(
    req: HttpRequest<any>,
    next: HttpHandler
  ): Observable<HttpEvent<any>> {
    this.requests.push(req);
    this.spinnerService.isLoading.next(true);

    return new Observable((observer) => {
      next.handle(req).subscribe(
        (event) => {
          if (event instanceof HttpResponse) {
            this.removeRequest(req);
            observer.next(event);
          }
        },
        () => {
          this.removeRequest(req);
        },
        () => {
          this.removeRequest(req);
        }
      );
    });
  }

  private removeRequest(request: HttpRequest<any>) {
    const index = this.requests.indexOf(request);

    if (index >= 0) {
      this.requests.splice(index, 1);
    }

    this.spinnerService.loadingStop.next();
    this.spinnerService.loadingStop.complete();
    this.spinnerService.isLoading.next(this.requests.length > 0);
  }
}

The Spinner service implementation is as follows:

 constructor() {
    this.isLoading
      .pipe(debounceTime(100), delay(1000), takeUntil(this.loadingStop))
      .subscribe((status: boolean) => (this.loadingStatus = status));
  }

To achieve the desired behavior, I included the following code snippet:

.pipe(debounceTime(100), delay(1000), takeUntil(this.loadingStop))

However, despite these efforts, I am unable to get it to work as intended. How can I ensure the spinner is displayed when response time exceeds 1 second?

Answer №1

Utilizes the iif operator to halt loading immediately.

This is an example of how the interceptor should be structured:

constructor(private spinnerService: SpinnerService) { }

intercept(request: HttpRequest<any>, next: HttpHandler): Observable<HttpEvent<any>> {
  this.spinnerService.start(request.url);

  return next.handle(request).pipe(
    finalize(() => () => this.spinnerService.stop(request.url))
  );
}

Here is the loading service implementation:

@Injectable()
export class SpinnerService {
  private _loading: BehaviorSubject<boolean>;
  private _request: Set<string>;
  private _delayTime: number;

  constructor() {
    this._loading = new BehaviorSubject(false);
    this._request = new Set();
    this._delayTime = 1000;
  }

  isLoading(time?: number): Observable<boolean> {
    return this._loading.asObservable().pipe(
      // utilizing switchMap to interrupt previous events
      switchMap(isLoading =>
        // using iif to introduce delay only for true value
        iif(
          () => isLoading,
          of(isLoading).pipe(
            delay(time !== undefined ? time : this._delayTime),
          ),
          of(isLoading),
        ),
      ),
    );
  }

  start(request: string = 'default', delayTime?: number): void {
    if (delayTime !== undefined)
      this._delayTime = delayTime;

    this._request.add(request);
    this._loading.next(true);
  }

  stop(request: string = 'default'): void {
    this._request.delete(request);

    if (!this._request.size)
      this._loading.next(false);
  }
}

And this is how it should appear in the template:

@Component({
  selector: 'my-app',
  template: `<div *ngIf="isLoading$ | async">loading...</div>`,
})
export class AppComponent  {
  isLoading$: Observable<boolean>;

  constructor(private spinnerService: SpinnerService) {
    this.isLoading$ = this.spinnerService.isLoading();
  }
}

Answer №2

To eliminate the flickering of the loading indicator, a debounceTime(500) function was implemented in the spinner service (excluding multiple request handling).

@Injectable()
export class LoadingInterceptor implements HttpInterceptor {

  constructor(private spinnerService: SpinnerService) {}

  intercept(req: HttpRequest<any>, next: HttpHandler ): Observable<HttpEvent<any>> {
    this.spinnerService.start();
    return next.handle(req).pipe(finalize( () => this.spinnerService.stop()));
  }
}

debounceTime(500) within the spinner service is key:

export class SpinnerService {

  private readonly state = new BehaviorSubject<boolean>(true);
  readonly state$ = this.state.asObservable()
    .pipe(
       debounceTime(500), 
       distinctUntilChanged()
  );

  constructor() {}

  public start() {
    this.state.next(true);
  }

  public stop() {
    this.state.next(false);
  }
}

The following component demonstrates this functionality:

export interface Post {
  id: string;
  title: string;
  body: string;
}

@Component({
  selector: 'app-posts',
  templateUrl: './posts.component.html',
  styleUrls: ['./posts.component.css'],
})
export class PostsComponent implements OnInit {
  readonly posts$: Observable<Post[]> = this.httpClient
    .get<Post[]>('https://jsonplaceholder.typicode.com/posts')
    .pipe(shareReplay(1));

  readonly state$ = this.spinnerService.state$;

  constructor(
    private spinnerService: SpinnerService,
    private httpClient: HttpClient
  ) {}

  ngOnInit() {}
}

HTML:

<p>List of Posts</p>

<ng-container *ngIf="(state$ | async);  else printResult">
  <h1>Loading...</h1>
</ng-container>

<ng-template #printResult>
  <ng-container *ngIf="posts$ | async as posts">
    <p *ngFor="let post of posts">
      {{ post.title }}
    </p>
  </ng-container>
</ng-template>

While the interceptor solution works effectively, there are more nuanced options available for displaying loading indicators for multiple parallel requests/components. For further insights, refer to this blog post by Nil.

Various solutions exist to address your specific issue. Trust this information proves beneficial.

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

Effortlessly glide through entire pages using the mouse wheel for seamless scrolling

I provide a seamless full-page scrolling experience using the mouse wheel. However, the scrollIntoView function does not seem to function within the @HostListener('wheel', ['$event']). Here is a snippet from app.component.html file: & ...

Setting an attribute on a custom component that is dynamically created within an Angular application

I am working with a custom library component called <my-icon>. To display the icon, I need to specify the property [name] like this: <my-icon [name]='warning'></my-icon> Currently, I am dynamically creating these icons in Type ...

Is it advisable to deactivate change detection during the initialization phase of an Angular app?

Just to provide some background: My Angular 5 application goes through a startup cycle that involves: HTTP calls to server APIs (checking token, retrieving user data) Local storage (database) calls A significant initialization process to prepare and tra ...

Exploring Angular 2 Paper-Input for Effective Form Management

I've been working on implementing Ng2 FormBuilder with paper-inputs and have successfully managed to get the bindings and validation up and running. You can check out my progress here: https://plnkr.co/edit/tr1wYZFyrn4uAzssn5Zs?p=preview <paper-i ...

Stop angular material css styles from affecting neighboring components

Currently, I have overridden the angular material style in my global SCSS style as follows: .mat-form-field-infix{ padding: 0.5em 0 0.5em 0 !important; } However, I now need to apply a different padding to the same element in my component for addition ...

Oops! An unexpected error occurred: TypeError - Seems like _this.searchElementRef is not defined

I recently implemented the Google Place API in my project by following this tutorial. However, I encountered the following error: ERROR Error: Uncaught (in promise): TypeError: Cannot read property 'nativeElement' of undefined The issue seems ...

The issue arises when Jest fails to align with a custom error type while utilizing dynamic imports

In my project, I have defined a custom error in a file named 'errors.ts': export class CustomError extends Error { constructor(message?: string) { super(message); Object.setPrototypeOf(this, Error.prototype); this.nam ...

Typescript objects may contain keys that are dependent on certain parameters

I have a challenge with constructing an object that requires querying multiple database tables, resulting in a time-consuming process. To address this issue, clients of the object need to specify which specific parts they require. For example, let's c ...

The Expo TypeScript template highlights JSX errors such as "Cannot assign type 'boolean' to type 'View'. TypeScript error 2322 at line 5:10:5"

Just starting out with Expo and decided to dive in with the typescript template using the npx create-expo-app -t expo-template-blank-typescript command. However, I'm running into some JSX type errors that are popping up even though the Expo server see ...

Tips for adjusting the radio button value similarly to a checkbox in Angular 2 using *ngFor

my checkbox and radio button implementation: <input id="{{k.group_name}}_{{i}}" name="{{k.group_name}}" type="checkbox" class="hide" name="{{k.group_name}}" [value]="m.details" (change)="change($event, m , k.item_ingredient_group_key,false,k.maximum)"& ...

Error NG0304: The element 'mat-select' is not recognized in the template of the 'LoginPage' component

After creating a basic app, I decided to incorporate Angular Material into my project. The app in question is an Ionic 6 / Angular 14 app, however, I encountered an error while attempting to implement mat-select: https://i.sstatic.net/Quc53.png To addres ...

The production build for Angular 9 Keyvalues pipe fails to compile

After successfully running my app on the development server with ng serve, I encountered a failure when trying to build it for production. The error message that popped up was: ERROR in src/app/leaderboard/leaderboard.component.html(9,17): Argument of typ ...

Utilizing ngx admin components for efficient development in Angular 6

I have been working with NGX Admin and I have encountered an issue with reusing components. Specifically, I am trying to integrate smart tables and pie charts into the Dashboard, but I keep running into a template parse error. To address this, I made sure ...

"Exploring the Angular 3 router's wildcard route matching feature

Why does the following route configuration always navigate to ** instead of the route for app/jungle? import {bootstrap} from '@angular/platform-browser-dynamic'; import { RouterConfig, provideRouter } from '@angular/<a href="/cdn-cgi/ ...

Can the SharePoint Graph API be accessed in Angular without requiring Azure app registration delegates and application permissions?

After creating our Angular application, we successfully implemented single sign-on using Azure app registration and MSAL library. Our goal is to access the SharePoint document graph API without requiring delegate or application level permissions in the ap ...

Retrieve an array containing objects with a subset of their properties. Typescript

Consider the array 'radicados' below: this.radicados = [{ id:0, asunto:'Facturas ADPRO Propias', consecutivo:'FAC-AB-00046', documentos: [{id:1, descripcion:'documento1.pdf', esAnexo:false, r ...

Combining the power of Visual Studio Code with NodeJs allows for seamless detection of missing package namespaces

Recently, I've encountered a frustrating problem. It occurs when: I create a new Node project without any installed modules I use import '' and press ctrl+space between the brackets, resulting in unnecessary inferred namespaces. Alth ...

How to set a default option in a dropdown menu using Angular 4

Many questions have been raised about this particular issue, with varying answers that do not fully address the question at hand. So here we go again: In my case, setting the default value of a dropdown select by its value is not working. Why is that so? ...

Decoding Angular Ivy: Understanding the Relationship between a Text Node and its ng-template

I am currently working on tests to verify that a text node of tpl1 is associated with an ng-template after it has been rendered. 1 <ng-template tpl> tpl1 </ng-template> 2 After rendering, the debugNode of tpl1 should point to the parent node ...

Is there a way to incorporate npm install --force into the process when running ionic build android?

When I ran the following command: ionic capacitor sync android I encountered conflicts with certain modules: C:\Users\DELL\Documents\inspecciones\inpecciones-app>ionic capacitor sync android > npm.cmd i -E @capacitor/<a h ...