Continue from previous parts [1 (opens new window)][2 (opens new window)] in this part, we will go through a few advanced techniques to implement the infinite loading feature to get more posts from HackerNews and skeleton gradient animation to deliver the best user experience. Although there are Vue Plugins to support infinite loading, in this tutorial, we will build from scratch so that we could learn deeply through the progress. Finally, due to the limitation of HackerNews API, we have a chance to use the new feature of ES2018 await for of
As usual, I will go through step by step with detailed explanations on why and how we code.
Firstly, we need to analyze the API from HackerNews to draft a basic design to implement infinite loading. From the official document, HackerNews API is built on top of Firebase, and we can only up to 500 recent items, and the result is a list of IDs below.
// 20210529073748
// https://hacker-news.firebaseio.com/v0/topstories.json?limitToFirst=10&orderBy=%22$key%22
[
27317655,
27321754,
27321780,
27288079,
27319540,
27316115,
27321387,
27301260,
27301651,
27301210
]
Hence, in order to paginate items, we may slice
as below:
// Query next 10 items for page 2
let resp = await api.get(`topstories.json?limitToFirst=20&orderBy="$key"`);
let result = resp.data.slice(10, 20);
Next step, we design the functions to handle data from API and then render it when the user scrolls to load. From the previous tutorials, we just load all of them at once, so this isn't gonna be a problem. However, when we gonna implement the infinite loading feature, this could cause an issue.
Let's examine, firstly, how to load items infinitely. We will use the event scroll
like this:
window.onscroll = () => {
window.innerHeight + window.scrollY >= document.body.offsetHeight && this.handleScroll();
};
Basically, the chunk of code above will listen on the event scroll
and whenever the user reaches the bottom of the page, the function handleScroll
would be invoked to render new items. However, in production, we need to make a lot of queries due to the limitation of 3rd party API.
[
27317655, // 1st request -> https://hacker-news.firebaseio.com/v0/item/27317655.json
27321754,
27321780,
27288079,
27319540,
27316115,
27321387,
27301260,
27301651,
27301210 // 10th request -> https://hacker-news.firebaseio.com/v0/item/27301210.json
]
All of these activities are asynchronous, handling by mechanism from Promise
object. So, even all of these items are not resolved, the event scroll
still being listened and keep invoking handleScroll
function.
To sum up, there are technical requirements to implement:
handleScroll
to be invoked when API result still not be resolved yetscroll
event to listen, when no more items loaded from APITo deal with the first task, a boolean
variable loading
would be created in a global state, so that we could tracking when the request is initialized to turn it on and set it off when all data is settled. We also create another boolean
variable endPagination
to track if there are no more items to load for a particular topic. Finally, an int
variable page
to manipulate pagination. Then our global state configuration would be like this.
state: {
topic: "top",
loading: false,
page: 1,
items: [],
endPagination: false,
},
mutations: {
setTopic(state, topic) {
state.topic = topic;
},
setLoading(state, status) {
state.loading = status;
},
setEndPagination(state, status) {
state.endPagination = status;
},
setPage(state, page) {
state.page = page;
},
increasePage(state) {
state.page++;
},
loadItems(state, items) {
state.items = [...state.items, ...items];
},
clearItems(state) {
state.items = [];
},
},
Next, we gonna handle API requests. There are a few ways to process multiple asynchronous requests such as using Promise.all()
, still, I would like to introduce you to how to make use of a new and very useful feature of ES2018: asynchronous iteration (opens new window) for await ... of
As this syntax is fairly new, so we need babel
to make it works on browsers.
Firstly, install a few plugins and @babel/core
yarn add -D @babel/core @rollup/plugin-babel @babel/plugin-proposal-async-generator-functions
Create new file .babelrc
{
"plugins": ["@babel/plugin-proposal-async-generator-functions"]
}
Then modify vite.config.js
import vue from "@vitejs/plugin-vue";
import { babel } from "@rollup/plugin-babel";
/**
* @type {import('vite').UserConfig}
*/
export default {
plugins: [vue(), babel({ babelHelpers: "bundled" })],
};
You can read more about the babel transform plugin here (opens new window). Then, we are good to go.
In order to implement this feature, we gonna write an async function generator.
async function* asyncGetter(data) {
let i = 0;
while (i < data.length) {
let res = await api.get(`item/${data[i]}.json?print=pretty`);
i++;
yield res.data;
}
}
The above async function will generate values, which conform to the async iterable protocol, then they can be looped using for await ... of
The mechanism is crucial, to synchronize requesting activities so that we can handle the event on
as I mentioned from the beginning. Here is the complete code at store\index.js
Skeleton loading pattern is not new and is widely adopted, to enhance the user experience when loading more content. It's not difficult to apply it to our project. So that, I just briefly write up about how it works, the code is already in the Github repo.
All we need to do is to create another Vue Component with the required CSS to style and animate the background. The trick is we need to set how the number of skeleton posts, conformed with the pagination system. To achieve that goal, we write a concise function to generate a list of number to be used with v-for
range(start, end) {
return Array.apply(0, Array(end - 1)).map((element, index) => index + start);
},
You can check out the complete code of components\Skeleton.vue
in the repo
There is another new component components\Modal.vue
to create modal effect for text posts such as Ask. This component is used a new Vue 3 API, teleport (opens new window) . I will leave it for your own exploration. It's quite easy to follow.
In this part, I think the most important lesson is how we analyze, plan and implement desired features. By planning ahead, we gonna avoid many bugs and save a lot of time in the coding implementation. More than that, we also have a chance to get familiar with ES2018 syntax for await ... of
in a practical project.
The final source code is on Github repo (opens new window). Live Demo (opens new window)
Photo by Angely Acevedo on Unsplash