How can you display a loading indicator after a delay using Observables, but make sure to cancel it if the loading is completed

Within my customer-detail component, I have implemented code that achieves the desired outcome. However, I believe there might be a more reactive and observable way to approach this task.

Instead of using an if statement to set this.isLoading = true;, is there a method to accomplish this utilizing reactive programming techniques? For instance, by terminating or disregarding the delayed observable if the customer data is fetched first? Or could there be a different approach altogether?

export class CustomerDetailComponent implements OnInit {

  customer: Customer;
  errorMessage: string;
  isLoading: boolean;

  constructor(
    private customerService: CustomerService,
    private route: ActivatedRoute,
    private router: Router,
    private location: Location
  ) { }

  ngOnInit() { 
    let idParam = this.route.params
      .distinctUntilChanged(params => params['id']);

    idParam.subscribe(params => 
    {
      this.errorMessage = ''; 
    });

    idParam.delay(300).subscribe(params => 
    {
      if (!(this.customer && this.customer.id == params['id']))
        this.isLoading = true;
    });

    idParam.switchMap((params: Params) => this.customerService.getCustomer(params['id']))
      .subscribe(customer => 
      { 
        this.customer = customer; 
        this.isLoading = false;
      },
      error => this.errorMessage = error);
  }
}

Answer №1

If you're looking to create a function that retrieves customer information, here's an example:

function fetchCustomerData(id) {
    return Observable.of({'name': 'Jane', id}).delay(1000);
}

Observable.of({'id': 55})
    .distinctUntilChanged(parameters => parameters['id'])
    .do(() => {
        // this.message = '';
    })
    .switchMap((parameters) => {
        return Observable.combineLatest(
            Observable.of(true).delay(500).startWith(null), // delay Observable
            fetchCustomerData(parameters['id']).startWith(null), // customer data Observable
           function(delayedItem, customerData) { // selection function
                if (customerData) {
                    return customerData;
                }

                if (delayedItem && !customerData) {
                    console.log('this.isLoading = true;');
                }
                return null;
            })
            .filter(customerData => customerData)
            .distinctUntilChanged(customerData => customerData['id']);
    })
    .subscribe(
        customerData => {
            console.log('Loading complete.');
            console.log(customerData);
            // this.customerDetails = customerData;
        },
        error => {
            // this.message = error;
        }
    );

Check out the live demo here: https://jsbin.com/yamezix/3/edit?js,console

The combineLatest() method in this snippet takes two Observables:

  1. A 500ms delay
  2. The customer data from a remote source (simulated in this case)

There's also a projection function employed to determine what should be passed on next. Both Observables use .startWith(null) to ensure they emit at least one item, triggering the combineLatest() when either changes. This helps us track which of the two initially emits - the delay or the customer data.

Additionally, there's a filter() call to eliminate all null values and a distinctUntilChanged() to prevent duplicate emissions of the same customer data (in case the customer completes first).

When we execute the code and the delay occurs first, the output will be as follows:

this.isLoading = true;
Loading complete.
{ name: 'Jane', id: 55 }

This indicates that loading is displayed before being hidden again.

And if we modify the completion time for fetchCustomerData() like so:

function getCustomerData(id) {
    return Observable.of({'name': 'Jane', id}).delay(200);
}

We would observe the following output:

Loading complete.
{ name: 'Jane', id: 55 }

In this scenario, no loading indicator is shown at all.

Answer №2

Check out this innovative rxjs 6 technique using a custom operator:

export function displayWithDelay<T>(delay: number, showLoading: () => void, hideLoading: () => void): OperatorFunction<T, T> {
  const loadingDisplayed$ = timer(delay).pipe(
    tap(() => showLoading()),
    mapTo(true),
    startWith(false)
  );

  return (source$) =>
    combineLatest([source$, loadingDisplayed$]).pipe(
      take(1),
      map(([data, delayDone]) => {
        if (delayDone) {
          hideLoading();
        }

        return data;
      })
    );
}

myStream$.pipe(displayWithDelay(300, () => this.showLoader = true, () => this.showLoader = false));

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

What is the method for exporting all functions from a TypeScript file with a default export included?

An example of the export type mentioned is React. To use it, we can include the following code: import React, {Component} from "react". This allows us to access both React.Component and Component. What steps are necessary to make this possible? ...

Tips for sending data through BLE with Ionic 3 and Angular 4

Here is my first question. I am currently utilizing the cordova-plugin-ble-central plugin to transfer data through my BLE device. I am struggling to grasp the process of sending data. My objective is to transmit a group of 8 bytes using a Unit8Array. Th ...

Guide to adding a class to dynamically generated Angular Material elements

Regarding a previous discussion on Stackoverflow, where there was an attempt to target the dynamically rendered Angular Material component class cdk-overlay-container through a class selector using a directive. The main objective was to add a specific clas ...

Calling GraphQL mutations in ReactPGA

I encountered a 400 Error when making a call from the client to server, and I'm not sure where to start troubleshooting. Oddly enough, when I only include the "id" parameter in the request, everything works fine. However, as soon as I add the additio ...

The Chrome debugger fails to display variable values when hovering the mouse over them

After creating a basic React app using the command "npx create-react-app my-app --template typescript", I encountered an issue where the values were not appearing in Chrome dev tools when I added a breakpoint in the code. Is this expected behavior for a Re ...

Continuously verify if there are any active child elements

I am dealing with a recursive list of items in Angular/TypeScript. My goal is to only show items when they are either active=true; themselves or if any of their children or grandchildren are also active=true. data.json [ { "active": ...

Issue encountered during ng2-redux installation

While attempting to use the https://www.npmjs.com/package/ng2-redux link, I encountered an issue when running npm install redux ng2-redux --save. Here is the stacktrace of the error: npm ERR! Windows_NT 6.1.7601 npm ERR! argv "C:\\Program Files& ...

It appears that when importing from a shared package in lerna, the name must include "src" at the end for Typescript or Javascript files

I am currently working on a straightforward lerna project structure as shown below: Project | +-- packages | | | +-- shared | | | | | +-- src | | | | | +-- index.ts | | +-- someDir | | | +-- usesShared | ...

Creating and updating a TypeScript definition file for my React component library built with TypeScript

As I work on developing a React library using TypeScript, it is important to me that consumers of the library have access to a TypeScript definition file. How can I ensure that the TypeScript definition file always accurately reflects and matches the Java ...

Different ways to pass a component function's return value to a service in Angular

On my HTML page, I am presenting job details within Bootstrap panels sourced from a JSON array using an ngFor loop. Each panel showcases specific job information along with a unique job ID. The panel is equipped with a click event which triggers the execut ...

Learn how to dynamically activate an icon in Angular to enhance user interaction

HTML Code: The Zoom Component <div class="zoom py-3"> <i nz-icon nzType="minus" (click)="zoomToggle(false)" nzTheme="outline"></i><br> <i nz-icon nzType="plus" (click)=&q ...

Defining a state in Typescript and passing it as a prop

export interface ISideBarProps { open: boolean; setOpen: React.Dispatch<React.SetStateAction<boolean>>; } export default function SideBar({ open, setOpen }: ISideBarProps) { return ( <div className={`absolute left-0 top-0 h- ...

Embedded template does not utilize property binding ngif with any directive

I am currently working on an Angular (Angular2 RC4) application and I'm facing some challenges running it with the live server in nodejs. Any suggestions on how to troubleshoot the error showing up in the Chrome console would be greatly appreciated. ...

A Comprehensive Guide to Handling Errors in Angular 4: Passing the err Object from Service to Component

Currently, I am in the process of developing a login page using Angular for the front-end and Spring Security for the back-end. Everything appears to be functioning correctly, except for handling exceptions when attempting to catch errors from the service ...

When working on styling a different Styled Component, how should one define the type of props required?

I'm currently working on a NextJS project using styled components and typescript. I have customized a div element like this: export const ClippedOverlay = styled( ( props: React.DetailedHTMLProps< React.HTMLAttributes<HTMLDivElement& ...

Strategies for successfully passing mock dates as event values when unit testing in Angular

I have a function that requires date data from a datepicker event. I am using matdatepicker for selecting a date from the UI. I need help passing the date event value to my onDateSelected() function. Could someone assist me in passing the date event valu ...

Guide to leveraging clsx within nested components in React

I am currently using clsx within a React application and encountering an issue with how to utilize it when dealing with mappings and nested components. For instance: return ( <div> <button onClick={doSomething}>{isOpened ? <Component ...

When invoking a callback function that includes a conditional type, TypeScript mandates the inclusion of a parameter that intersects multiple types

There is a function that requires specific arguments, including a callback function that takes an object or an array of objects based on an isArray parameter. I am attempting to create a new feature. type Option = { name: string value: string } type ...

Detecting Changes in Angular Only Works Once when Dealing with File Input Fields

Issue arises with the file input field as it only allows uploading one file at a time, which needs to be modified. Uploading a single file works fine. However, upon attempting to upload multiple files, it appears that the "change" handler method is not tr ...

Encountering the error "TypeError: null is not an object (evaluating '_ref.user')" with onAuthStateChanged in React Native using Firebase and useContext

I'm encountering an error in my useCachedResources.ts file and I'm uncertain of the cause. These three files are what I'm currently working with. I have a suspicion that the issue lies in the initial null value, but I am conditionally render ...