DEV Community

Cover image for Faking UX Race Conditions using a simple Interceptor in Angular or NestJs
ElAndyG
ElAndyG

Posted on • Updated on

Faking UX Race Conditions using a simple Interceptor in Angular or NestJs

see it in action - StackBlitz

Race conditions are easy to understand. They’re just like a high-five which depends on 2 people slapping hands at the same exact time. If one person is out of sync, the other may get a high-five to the face 🤦‍♂️. Boom! Race conditions explained!

Do you even high-five?

In this article, we are going to talk about UX Race Conditions and using a simple interceptor to exaggerate delays from the API so that you can see if you are giving your users the best experience possible.

UX Race Conditions

In the classic sense, race conditions can cause bugs and unintended manipulation of your data. @vsavkin shows us an example of how a user can trigger a new request before the last request had finished, causing the wrong data to be displayed.

But let's talk about the User Experience. Even if all of your data came in on time, could the User Experience also suffer from its own, sort-of, race condition? Using the image below, would the random presentation of components still convey the correct intent?

random-loading-site

The delays in presentation would trigger a "dizzying" experience because you are shifting the user's eye-line as components are coming in. And since they have no choice to what is presented first, you could be causing the user to context-switch until the information they want is displayed.

Exposing Race Conditions

Sometimes our developer machines are just so darn good that we don't notice the delays in presentation. So let's slow things down with a simple interceptor to exaggerate these delays, by a lot.

// Angular
@Injectable()
export class DelayInterceptor implements HttpInterceptor {
  intercept(
    httpRequest: HttpRequest<any>,
    next: HttpHandler
  ): Observable<HttpEvent<any>> {
    const delayInMs = Math.random() * 5000; //creating a delay of up to 5 seconds.

    console.log(`DelayInterceptor: delaying for ${delayInMs}ms.`);

    return next.handle(httpRequest).pipe(delay(delayInMs));
  }
}
Enter fullscreen mode Exit fullscreen mode

This interceptor will add a random delay of up to 5 seconds on all of your HTTP Requests for the entire application.

By exaggerating the delay of data coming back from your endpoints, you can now force components to be presented at different intervals, allowing you the opportunity to see if they are coming in in a strange order and if the user's experience could use a little bit of adjusting.

2 different loading scenarios

What should you be doing?

In the example above, we are using a forkJoin to combine all of the subscriptions so that we get all of the results back at the same time. We can now add a spinner or "loading message" to prompt the user to wait, limit the distractions and provide you with a "worst case scenario" if your production servers were to drag.

What are the cool kids doing?

If you take a look at #slack or YouTube, you'll notice they add placeholders called "skeletons" which lets their users know that they are going to fill a particular space. This is a neat solution to the problem because it will pre-fill the space instead and, as css-tricks states, provide the "illusion of speed." Below is an an example of that.

skeleton

Conclusion

The concept of race conditions can also be applied to the user experience. By exaggerating load times, you can simulate a slow network so that you can understand what the user sees and prepare for it. As you think about the flow of data, include "how it is presented" so that you can provide a more enjoyable user experience. Or at the least, help you find that logical race condition issue 😀. It also works for that too!

Bonus

Below is the same interceptor for NestJs. If you are connecting to a local API, or maybe you are using React, Vue etc, you can force the same delay from your API instead.

// Nest.js 
@Injectable()
export class DelayInterceptor implements NestInterceptor {
  intercept(context: ExecutionContext, next: CallHandler): Observable<any> {
    const delayInMs = Math.random() * 5000;

    console.log(`DelayInterceptor: delaying for ${delayInMs}ms.`);

    return next.handle().pipe(delay(delayInMs));
  }
}
Enter fullscreen mode Exit fullscreen mode

Top comments (0)