`Angular RxJS vs Vue Reactivity: Best practices for managing UI updates that rely on timers`

How can you implement a loading spinner following an HTTP request, or any asynchronous operation that occurs over time, using the specified logic?

  • Wait for X seconds (100ms) and display nothing.

  • If the data arrives within X seconds (100ms), display it immediately.

  • If the data does not arrive within X seconds (100ms), display the spinner for at least Y seconds (250ms) until the data arrives.

In Angular/RxJS, you would achieve this in your Service by:

/**
     * We want to display the loading indicator only if the requests takes more than `${INITIAL_WAITING_TIME}
     * if it does, we want to wait at least `${MINIMUM_TIME_TO_DISPLAY_LOADER} before emitting
     */
    const startLoading$ = of({}).pipe(
      tap(() => this.setState({ loading: true })),
      delay(MINIMUM_TIME_TO_DISPLAY_LOADER),
      switchMap(() => EMPTY)
    );

    const hideLoading$ = of(null).pipe(
      tap(() => this.setState({ loading: false }))
    );

    const timer$ = timer(INITIAL_WAITING_TIME).pipe();

    /**
     * We want to race two streams:
     *
     * - initial waiting time: the time we want to hold on any UI updates
     * to wait for the API to get back to us
     *
     * - data: the response from the API.
     *
     * Scenario A: API comes back before the initial waiting time
     *
     * We avoid displaying the loading spinner altogether, and instead we directly update
     * the state with the new data.
     *
     * Scenario B: API doesn't come back before initial waiting time.
     *
     * We want to display the loading spinner, and to avoid awkward flash (for example the response comes back 10ms after the initial waiting time) we extend the delay to 250ms
     * to give the user the time to understand the actions happening on the screen.
     */
    const race$ = race(timer$, data$).pipe(
      switchMap((winner) =>
        typeof winner === 'number' ? startLoading$ : EMPTY
      )
    );

    return concat(race$, hideLoading$, data$).pipe(filter(Boolean));

Alternatively, in Vue, you can use setTimeout and nested watch to handle changes to reactive properties like so:

Link to Vue Playground

<script setup lang="ts">
import { ref, watch } from 'vue'

const displayUI = ref<boolean>(false);
const loading = ref<boolean>(false);
const data = ref<string | null>(null);
  
setTimeout(() => {
  // after 100ms we want to display a UI to the user
  displayUI.value = true;
  
  // if data has arrived, we can display and exit this logic
  if (data.value) {
    return;
  }

  // if it has not arrived
  // we show spinner for at least 250ms
  loading.value = true;

  setTimeout(() => {
    // at this point, we should display data, but only after data has arrived
   // Question is: without RxJS, how can we 
   if (data.value) {
     loading.value = false;
   } else {
     // can we nest a watcher?
     watch(data, (value) => {
     if (value) {
       loading.value = false
       data.value = 'it worked!'
     }
   })
   }
  }, 2500)
    
    
}, 1000) 

// fake timer, let's say our API request takes X amount of time to come back
setTimeout(() => {
  data.value = 'Data arrived'
}, 4000)
  

</script>

<template>
  <template v-if="displayUI">
    <h1 v-if="!data && !loading">
    No Data
  </h1>
  <h1 v-if="!loading && data">
    {{ data }}
  </h1>
  <h1 v-if="loading">
    Loading...
    </h1>
  </template>
</template>

Answer №1

Angular relies heavily on observables within its ecosystem and framework. However, when there isn't a clear use case for them, it's often better to stick with promises for handling asynchronous code.

Instead of placing asynchronous side effects in the setup body, they should be handled in lifecycle hooks:

onMounted(async () => {
  const dataPromise = getData();

  let result = await Promise.race([delay(100), dataPromise])

  if (!result) {
    loading.value = true;

    // not awaited
    Promise.all([delay(250), dataPromise]
      .finally(() => loading.value = false);

    result = await dataPromise;
  }

  data.value = result;
});

To implement a loader using Vue suspense feature, you need to separate the component into two parts - the parent with the suspense tag hosting, and the child performing the asynchronous work using script setup and await.

Answer №2

Big shoutout to @Etus Flask for pointing me in the right direction with his helpful answer. Even though I'm familiar with using Observable for this kind of logic, I completely overlooked the existence of Promise.race.

If you're interested, check out the example I put together in the Vue Playground. It showcases the feature fully implemented.

I've also included some time logs so you can track the timeline of different steps involved.

<script setup>
  
import { ref, onMounted, reactive } from 'vue';
  
const loading = ref(false)
const data = ref(null); 
  

onMounted(async () => {
  const p = getData();
  const res = await Promise.race([delay(100), p])
  
  if (!res) {
    loading.value = true;

    await delay(250);
    const d = await p;

    // assign data for UI to display
    loading.value = false;
    data.value = d;
  } else {
    data.value = res;
  }
})

async function delay(time){
  return new Promise((res) => {
    setTimeout(res, time)
  })
}
  
 // we simulate a network request
 async function getData() {
   return new Promise(res => {
     setTimeout(res, 200, {data: true})
   });
  }
  
</script>

<template>
  <h1 v-if="loading">
    Loading...
  </h1>
   <h1 v-if="!loading && data">
    Data
  </h1>
</template>

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 best way to display jQuery/AJAX response in a table cell?

I am struggling with a script that retrieves data from a SQL database query and need help placing the result in a specific table cell. Here is the query: <script type="text/javascript"> $(document).ready(function(){ $('.typeval').change(f ...

Can you explain the significance of the v-on="..." syntax in VueJS?

While browsing, I stumbled upon a Vuetify example showcasing the v-dialog component. The example includes a scoped slot called activator, defined like this: <template v-slot:activator="{ on }"> <v-btn color="red lighten-2" ...

Activate the Jquery-ui Tooltip with a click event

Here is the code I'm using: function DrawTipsProgress(postid, ajaxurl) { var data = { action: 'ajax_action', post_id: postid } jQuery('#dashicon-' + postid).on("click", function () { jQuery.p ...

I'm baffled by the fact that my routes appear to be non-existent. I cannot comprehend the reason behind this issue

I'm fairly new to web development, and I've been struggling with this issue for the past hour. Even after simplifying my code to the bare minimum, it's still not working. Here's what I have so far: app.js: const express = require(&apo ...

How can one determine when an animation in Three.js has reached its conclusion?

When I write code like this: function renderuj(){ scene.renderer.setClearColor(0xeeeeee, 1); ob1.animation.update(0.5); ob2.animation.update(0.5); scene.renderer.render(scene.scene, scene.camera); animationFram = reques ...

Leveraging the power of v-model and refs within a slot component in Vue.js version

One interesting challenge I encountered involves a component that takes a main <slot> from a form generated elsewhere in my application. Despite defining the properties, when attempting to use v-model on the form inputs, Vue throws a warning claiming ...

Node is looking for a callback function, but instead received something that is undefined

When attempting to build a basic CRUD app in node js, an issue arises with the error message "Route.get() requires a callback function but got a [object Undefined]" specifically on the router.get("/:id", userController.getUser); line. Routes.js const expr ...

Extracting and retrieving information from a complex data structure obtained through an API call

Struggling with this one. Can't seem to locate a similar situation after searching extensively... My goal is to determine the author of each collection associated with a user. I wrote a function to fetch data from an API for a specific user, in this ...

What is the best way to access the data stored within a Promise object in a React application?

Below is the snippet of my code that handles parsing application data: async function parseApplication(data: Application) { const fieldGroupValues = {}; for (const group of Object.keys(data.mappedFieldGroupValues)) { const groupValue = data.mappedF ...

Could one potentially generate new static files in Nextjs without needing to rebuild the entire app?

After recently beginning to utilize NextJs' getStaticProps feature, I have found that the static files generated at build time are quite impressive. However, my content is not static and requires updates without having to rebuild the entire app each t ...

Using jQuery to eliminate accepting input from form field

Looking to switch between a URL input and file input based on user selection of a radio button. Encountering an issue when attempting to remove the accept attribute from the input, resulting in an Uncaught TypeError: $(...).get(...).removeAttr is not a fu ...

Use map.fitBounds in MapBox to continuously adjust the map view to show only the visible features on the map

In my map, there are various features with IDs stored in an array called featureIds. Within my application, I have a button that can toggle the visibility of certain features. To address this toggling behavior, I am developing a JavaScript function named ...

The identical page content is displayed on each and every URL

Implementing a multi-step form in Next JS involves adding specific code within the app.js file. Here is an example of how it can be done: import React from "react"; import ReactDOM from "react-dom"; // Other necessary imports... // Add ...

Display the Currently Searched Value with Select2

I am currently utilizing the Select2 framework along with an ajax call in my project. I have successfully implemented it, but I am facing an issue where the current search value is being displayed as a selectable option even when there are no search result ...

The function get_template_directory_uri() returned an unexpected string error

Exploring WP themes has been an interesting journey for me. Currently, I am working on creating a shortcode for some html/script and encountering an issue with enqueuing the js file. My initial query is about whether I am loading this from the correct loc ...

Is there a way to use JavaScript to rearrange the order of my div elements?

If I have 4 divs with id="div1", "div2", and so on, is there a way to rearrange them to display as 2, 3, 1, 4 using Javascript? I am specifically looking for a solution using Javascript only, as I am a beginner and trying to learn more about it. Please p ...

``Emerging Challenge in React: Ensuring Responsive Design with Fixed Positioning

I'm currently encountering a challenge with my React application. I've developed a website using React that includes a component named CartMenu, which is integrated within another component called Products. The issue arises when I utilize the de ...

The issue of calling the child window function from the parent window upon clicking does not seem to be functioning properly on Safari and Chrome

I'm attempting to invoke the function of a child window from the parent window when a click event occurs. Strangely, this code works in Firefox but not in Safari or Chrome. Here is the code snippet I am using: var iframeElem = document.getElementById( ...

Modify vanilla JavaScript carousel for compatibility with Internet Explorer

I am currently in the process of creating a website that incorporates a carousel similar to the one found at the following link: https://codepen.io/queflojera/pen/RwwLbEY?editors=1010 At the moment, the carousel functions smoothly on opera, chrome, edge ...

Preventing Bull Queue from automatically re-starting jobs upon server restart

Currently, I am utilizing the bull queue system for processing jobs. Imagine a scenario where a job is in progress with an active status and I restart my development server. Upon restarting the worker script, the job remains in the active state within the ...