Simple Asynchronous Infinite Scroll with Vue Watchers

Chris Nwamba

Infinite Scroll is a UX pattern that suggests showing users few contents on page or app load. More contents are then loaded once the user starts scrolling down the page. These contents are loaded asynchrounously by making request to the server responsible for providing the content. In this post I’m going to talk about asynchronous operations in JavaScript, as well as how Vue ‒ an awesome JavaScript framework, can be used in this context. In the process, we will see a simple page that uses infinite scroll.

Understanding Asynchronous Operations

In programs written synchronously, here’s the deal ‒ you’ve got two lines of codes, L1 and L2. L2 cannot execute until L1 has finished running. check out the most basic example of a synchronous written program below:

  console.log("quavo");
  console.log("takeoff");
  console.log("offset");

  //Console logs
  > quavo
  > takeoff
  > offset

The problem with writing code this way is that it makes your app's UX rigid and awkward. Let's say you visit a shopping website, say Amazon, if you're not running an ad blocker chances are you'll be flooded with ads. If Amazon was running on synchronously written code, your page would freeze up repeatedly while all of the images loaded for the pages and ads and you wouldn't be able to click on anything until after the page has finished loading. If the page loaded a lot in the background it could take a while before you could interact with the page which would be very inconvenient.

Enter asynchronous programs, they offer us a much better deal I think. You’ve got two lines of code, L1 and L2, where L1 schedules some task to be run in the future but L2 runs before that task completes. Check out this basic demo of writing asynchronous code:

  console.log "quavo"
  $http.get("our_url", (data) => console.log "takeoff")
  console.log "offset"

  // Console logs
  > quavo
  > offset
  > takeoff

Normally we would see the code above executed in the order in which it was written but because the $http.get request above will take some time to get data from our_url, JavaScript won’t wait for that, instead it goes ahead and executes the next line of code while waiting for $http.get to finish what it is doing thus the arrangement of logging in the console. Other ways of writing code asynchronously include:

  • setTimeout functions which schedule something to happen in the future and then go ahead to execute the next block of code:
  console.log("Can I take your order?");
  setTimeout(function() {
    console.log("Yes please!");
  }, 2000);
  console.log("Sure, one second please");

  // Console logs
  > Can I take your order?
  > Sure, one second please
  > Yes please!
  • Higher order functions (also known as callback functions) which are functions that are passed to other functions as parameters. Let’s assume a callback function named function X, it is then passed to another function called function Y as a parameter. Eventually function X is executed or called inside function Y. Check out the code block below:
  function Y(X) {
    $.get("our_url", X);
  }

Callback functions exist in different modes, chances are you’ve used them without knowing. Examples include window.onclick, setTimeout and setInterval.

What are Watchers?

Vue watchers allow us to perform asynchronous operations in response to changing data. They’re like a more general way to react to data changes in your Vue instance. In our Vue Instance, watchers are denoted with the watch keyword and are used thus:

  new Vue({
    el: '#app',
    data: {
      // data we intend to bind to our view
    },
    watch: {
      // asynchronous operations that we'll be working with in our app
    }
  });

Extending Asynchronous Operations with Watchers

Let’s see how Vue uses watchers to monitor asynchronous operations. Using Vue, we’ll build an app with an infinite scroll feature, once a user gets to the bottom of the page, a GET request is performed and more data is retrieved. Let me just chip in here that this article works through Sarah Drasner's original pen. I'm a big fan. Let’s get to work:

Amazon

  • Import the necessary tools via the DOM, using script tags. Here we’ll import Vue and Axios, a promise based HTTP client for the browser
  <head>
    <script src="https://unpkg.com/vue"></script>
    <script src="https://unpkg.com/axios/dist/axios.min.js"></script>
  </head>
  • Create a new Vue Instance:
  new Vue({
    //  The el property points to the DOM using a query selector
    el: '#app',
  });
  • Create the data function and append any data that should be bound to the DOM. Initially, we won’t be at the bottom of the page thus the bottom boolean property is set to false. Whatever results our API fetches will be stored in the beers property, which is an empty array.
  data() {
    return {
      bottom: false,
      beers: []
    }
  }
  • Define our methods using the methods member. Methods allow us create functions and bind events to these functions as well as handle these events. In the bottomVisible() function, we use three read-only properties to manually create our infinite scroll feature:
  1. scrollY: Returns the Y coordinate of the top edge of the current viewport. If there is no viewport, the returned value is zero.
  2. clientHeight: The inner height of an element in pixels, including padding but not the horizontal scrollbar height, border, or margin.
  3. scrollHeight: The height of an element's content, including content not visible on the screen due to overflow.

In the addBeer() function, we perform a GET request using Axios. Using promises and callbacks, we create an object apiInfo and pass in the values retrieved from our API call. Every function in our Vue Instance can access data properties using this.

  methods: {
    bottomVisible() {
      const scrollY = window.scrollY
      const visible = document.documentElement.clientHeight
      const pageHeight = document.documentElement.scrollHeight
      const bottomOfPage = visible + scrollY >= pageHeight
      return bottomOfPage || pageHeight < visible
    },
    addBeer() {
      axios.get('https://api.punkapi.com/v2/beers/random')
        .then(response => {
          let api = response.data[0];
          let apiInfo = {
            name: api.name,
            desc: api.description,
            img: api.image_url,
            tips: api.brewers_tips,
            tagline: api.tagline,
            food: api.food_pairing
          };
          this.beers.push(apiInfo)
          if (this.bottomVisible()) {
            this.addBeer()
          }
        })
    }
  }
  • The watch option watches for changes to the state of the app and updates the DOM accordingly:
  watch: {
    bottom(bottom) {
      if (bottom) {
        this.addBeer()
      }
    }
  }
  • We use the created lifecycle hook to add a scroll event that fires each time the bottomVisible() function is called. To implement our infinite scroll feature, we equate the bottom boolean property in the data function to the bottomVisible() function. created hooks allow us access reactive data as well as functions in our Vue instance.
  created() {
    window.addEventListener('scroll', () => {
      this.bottom = this.bottomVisible()
    })
    this.addBeer()
  }

Move over to the DOM, where the following Vue directives will be used to provide two way binding between the DOM and the Vue instance:

  1. v-if: For conditionally rendering statements on the DOM
  2. v-for: For rendering a list of items based on an array, requires a special syntax in the form of beer in beers, where beers is the source data array and beer is an alias for the array element being iterated on.

  3. v-if: For conditionally rendering statements on the DOM
  4. v-for: For rendering a list of items based on an array, requires a special syntax in the form of beer in beers, where beers is the source data array and beer is an alias for the array element being iterated on.
  <div id="app">
    <section>
      <h1>Make yourself some Punk Beers </h1>
      <div v-if="beers.length === 0" class="loading">Loading...</div>
      <div v-for="beer in beers" class="beer-contain">
        <div class="beer-img"> <img :src="beer.img" height="350" /> </div>
        <div class="beer-info">
          <h2>{{ beer.name }}</h2>
          <p class="bright">{{ beer.tagline }}</p>
          <p><span class="bright">Description:</span> {{ beer.desc }}</p>
          <p><span class="bright">Tips:</span> {{ beer.tips }}</p>
          <h3 class="bright">Food Pairings</h3>
          <ul>
            <li v-for="item in beer.food"> {{ item }} </li>
          </ul>
        </div>
      </div>
    </section>
  </div>

Original Pen by Sarah Drasner

Summary

One might ask “Why don’t we just use computed properties for this?”, the reason is that computed properties are synchronous and must return a value. When carrying out asynchronous operations such as timeout functions, or like our demo above, GET requests, it’s best to use watchers because Vue listens for the return value of the function. It’s also cool to use event listeners but these have the disadvantage of manually handling the event and calling a method instead of just listening to data changes. In any case, you now know how to handle asynchronous operations when you see or want to use them in Vue.

Chris Nwamba

48 posts

JavaScript Preacher. Building the web with the JS community.