Home > Mobile >  How to ensure minimum operation duration
How to ensure minimum operation duration

Time:03-08

I'm building an Angular Material mat-tree with dynamic data (based on this example).
When the user expend a node, a progress-bar is shown while the list of child nodes is retrieved from the server.

The base code looks like this:

toggleNode(node: DynamicFlatNode, expand: boolean) {
  if (expand) {
    // Shows progress bar
    node.isLoading = true;

    this
      .database
      .getChildren(node.id)
      .subscribe(
        values => {
          // Create and add the descendant nodes
          ...
        },
        error => { console.error(error); },
        () => {
          // Hides progress bar
          node.isLoading = false;
        });

  } else {
    // Remove descendant nodes
    ...
  }
}

The issue is that if the data retrieving is too quick, the progress bar just blink.
I would like to add a delay so the progress bar is shown at least, let say, 250ms.
I want the time of visibility to be min(250ms, data retrieving time) not 250 ms data retrieving time.

My current solution is to use a dummy promise, that starts before data retrieving and is awaited in the complete callback:

toggleNode(node: DynamicFlatNode, expand: boolean) {
  if (expand) {
    // Shows progress bar
    node.isLoading = true;

    const delay = new Promise(resolve => setTimeout(resolve, 250));
    this
      .database
      .getChildren(node.id)
      .subscribe(
        values => {
          // Create and add the descendant nodes
          ...
        },
        error => { console.error(error); },
        async () => {
          await delay;
          // Hides progress bar
          node.isLoading = false;
        });

  } else {
    // Remove descendant nodes
    ...
  }
}

Is there a more concise way of doing this?

CodePudding user response:

You could leverage the RxJS timer with forkJoin to introduce an artificial delay. I'd also suggest using the finalize operator to set the isLoading flag. If not you'd have to set it in both error and complete callbacks of the subscription.

Important: forkJoin will only emit when all it's source observables complete. So you need to make sure your observable this.database.getChildren(node.id) completes. If it doesn't you could force it to complete after the first emission using take(1), assuming you wouldn't need the emission stream.

Try the following

import { forkJoin, timer } from 'rxjs';
import { finalize, map, take } from 'rxjs/operators';

const DELAY = 250;

toggleNode(node: DynamicFlatNode, expand: boolean) {
  if (expand) {
    node.isLoading = true;                            // <-- show progress bar
    forkJoin({
      timer: timer(DELAY),
      children: this.database.getChildren(node.id),
    }).pipe(
      map(({ children }) => children),                // <-- ignore the `timer` emission
      finalize(() => (node.isLoading = false))        // <-- hide progress bar
    ).subscribe({
      next: (values: any) => {
        // create and add the descendant nodes
      },
      error: (error: any) => {
        // handle error
      },
    });
  } else {
    // remove descendant nodes
  }
}

Working example: Stackblitz. Play with the DELAY variable to observe the induced delay.

CodePudding user response:

You can grab idea from here

import { of, map, Observable, combineLatest, delay, timer } from 'rxjs';
import { mapTo, startWith, tap } from 'rxjs/operators';

const GET_DATA_DELAY = 100;
const PROGRESS_DELAY = 250;
const getData = (): Observable<any> => {
  return of('data').pipe(delay(GET_DATA_DELAY));
};
const log = {
  next: (value: any) => console.log(`Date:${ (new Date()).toISOString()} : `, value),
  error: (err: any) => console.error(err),
};
const delay$ = timer(PROGRESS_DELAY).pipe(mapTo('timer'),tap(log));
const data$ = getData().pipe(tap(log));
const isLoading$ = combineLatest([delay$, data$]).pipe(
  mapTo(false),
  startWith(true),
  map(value => `IsLoading: ${value}`),
);
isLoading$.subscribe(log);

// Open the console in the bottom right to see results.
  • Related