DEV Community

Cover image for Optimizing API calls in web components
Collin Kleest
Collin Kleest

Posted on

Optimizing API calls in web components

Overview

When building a web component you may need to grab a resource from an external entity. This is typically called an "API call", in this particular blog post I will go over how we can optimize API calls when fetching data using Javascripts fetch method and some extra tricks. Throughout this article, I will be referring to code in an element I wrote for elmsln/lrnwebcomponents web component mono-repo.
Here is the full source code of the element itself github-preview-source

Main Points

How the browser loads javascript

This may seem like a simple concept, you include your script on an HTML page and your javascript is loaded. Under the hood, your browser is doing a lot more than just loading that script. Javascript is built on the idea of asynchronous processing which is basically processing the code while the browser is doing other things to set up the page. This all happens very quickly but occurs in the browser's event loop.

The event loop sets tasks in a queue executing each task and waiting for it to finish, then executing the next task in the queue. This is important to understand because our API call will be registered as a task, queued behind our script because we encapsulate it in a setTimeout call. More on this later...

Making an API request using fetch

This may be straightforward but I am still going to cover it. Inside my web component, I will define a function called fetchGithubData. This function will accept some parameters needed to make the call to Github's API and return data to a handler method, which will save the data inside our element so it can display it.

fetchGithubData(apiUrl, org, repo){
   fetch(`${apiUrl}/repos/${org}/${repo}`)
      .then((response) => {
        if (response.ok) {
          return response.json();
        }
      })
      .then((json) => {
        this.handleResponse(json);
      })
      .catch((error) => {
        this.__assetAvailable = false;
        console.error(error);
      });
}
Enter fullscreen mode Exit fullscreen mode

The function takes in an apiUrl (https://api.github.com), a GitHub organization or user, and a repository name then feeds that data to our handler function.

handleResponse(response) {
    if (response) {
      this.__assetAvailable = true;
      this.__description = response.description;
      this.repoLang = response.language;
      this.__stars = response.stargazers_count;
      this.__forks = response.forks;
    }
  }
Enter fullscreen mode Exit fullscreen mode

Our data handler first checks if we got a response, if we do have a response it sets some properties that get rendered inside of our web component.

Here is what our web component looks like for reference.
Alt Text
We can see it contains some of the properties that we set in our handler method like title, the repos primary language, forks, description and stars.

Lit Element lifecycle methods

Since my element uses the lit-element library we will take advantage of the life cycle methods to make our API call. Lit element provides a couple of life cycle methods but the two we will look at are firstUpdated and updated.

The firstUpdated method gets called as soon as the DOM registers the element. The updated lifecyle method gets called immediately after firstUpdated and is where we will make our API call.

We want to make our call in the update function because if the repository or organization changes after the element has been mounted and rendered we can respond to that change because our element has been "updated". Take a look at this demo video to show why we use our API call inside the updated lifecycle method.

If you are interested in learning more about lit-elements life cycle methods visit their docs: https://lit-element.polymer-project.org/guide/lifecycle

Using timeout and debounce

So now I am going to show why and how we will use the Javascripts built-in setTimeout function to make our API call.

Here is the code in our updated life cycle method.

updated(changedProperties) {
    changedProperties.forEach((oldValue, propName) => {
      // only make the fetch after we get everything setup
      if (
        [
          "repo",
          "org",
        ].includes(propName) &&
        this[propName]
      ) {
        clearTimeout(this.__debounce);
        this.__debounce = setTimeout(() => {
          this.fetchGithubData(
            this.apiUrl,
            this.repo,
            this.org,
          );
        }, 0);
      }
    });
  }
Enter fullscreen mode Exit fullscreen mode

We use a forEach to go through each property that changed. You may be wondering well what about the initial properties set, these properties are still considered new, and are passed into the updated function when the component mounts.

Next, we check if the properties we want to consider are properties of the class. Then if there is already a timeout set in debounce variable we clear this. We do this to make sure we only make the API call once, so once our forEach gets to the last changed property the timeout won't clear and make the API call.

We use setTimeout because our browser will call this once all the javascript in the file has been processed. This allows the browser to make sure everything is in place before we make our API call. The setTimeout callback gets added to the browser's event loop queue and gets called immediately after it's interpreted all the other Javascript in the file.

Applying headers for caching

Lastly, we will apply headers to our request that tells the browser to cache our result (save it for later). This increases performance when your browser makes the same request, it checks the cache first if the response object is present it will use the cached response instead of making a new request.

We can set out headers to do this in the constructor of the element as shown here:

constructor() {
    super();
    this.url = "https://github.com";
    this.apiUrl = "https://api.github.com";
    this.rawUrl = "https://raw.githubusercontent.com";
    this.extended = false;
    this.readMe = "README.md";
    this.branch = "master";
    this.viewMoreText = "View More";
    this.notFoundText = "Asset not found";
    this.headers = {
      cache: "force-cache",
    };
  }
Enter fullscreen mode Exit fullscreen mode

Then we can use these headers in our fetch call.

fetch('https://someendpoint.com/git/', this.headers)
Enter fullscreen mode Exit fullscreen mode

Conclusion

That's it! If you are interested in learning more about web components and some of the stuff I referenced in this blog post check out the resources section below.

Resources

Socials

LinkedIn
Github

Top comments (0)