Making API Calls (part 2)

In the first part of this tutorial, we used an API call to create a list of pokemon. This tutorial will pick up where we left off and work through to the final result.

Pagination

Our API call returned an array with 20 objects, but it indicated there were a total of 811 objects available. To see the remaining pokemon we will need to retrieve each page of results. Fortunately, we can use our existing code to do so, we just need to make some minor adjustments.

const container = document.querySelector('.pokedex');
const pageOneUrl = "https://pokeapi.co/api/v2/pokemon";

const getPokemonList = (url) => {
  axios.get(url)
       .then( (response) => {
          const data = response.data.results;
     
          data.map( (pokemon) => {
            const div = document.createElement('div');
            const text = document.createTextNode(pokemon.name);
             
            div.appendChild(text);
            div.className += 'pokeList';
            container.appendChild(div);
          });      
       })
       .catch( (error) => {
          throw error;
       });
}

getPokemonList(pageOneUrl);

I’ve moved our variable holding the api url outside of the function and renamed it pageOneUrl. I’ve added url as a parameter to our getPokemonList function so we can pass in other urls as needed. Now any url provided to this function will be used for the API call, making it possible to reuse this function for any data we may receive in the same format as our pageOneUrl returns.

Next we need to make sure that our parent container, the div with the class pokedex is empty before we add anything to it. We can do this with just three lines of code:

const container = document.querySelector('.pokedex');
const pageOneUrl = "https://pokeapi.co/api/v2/pokemon";

const getPokemonList = (url) => {
  while (container.firstChild) {
    container.removeChild(container.firstChild);
  }

  axios.get(url)
       .then( (response) => {
          const data = response.data.results;
     
          data.map( (pokemon) => {
            const div = document.createElement('div');
            const text = document.createTextNode(pokemon.name);
             
            div.appendChild(text);
            div.className += 'pokeList';
            container.appendChild(div);
          });      
       })
       .catch( (error) => {
          throw error;
       });
}

getPokemonList(pageOneUrl);

This while loop looks in our container and checks for a child element, if one is found it removes that child. This loop continues until no more children are found. Piece of cake!

Next, we need some way to tell the app to load the next page. For this we will create buttons and add them to either side of our header. Let’s update the header html from this:

<div class='header'>Pokeapi Pokedex</div>

To this:

<div class='header'>
  <div class='nav' id='prev'>&lsaquo; Prev</div>
  Pokeapi Pokedex
  <div class='nav' id='next'>Next &rsaquo;</div>
</div>

In our css we’ll change the header to:

.header {
  display: flex;
  justify-content: space-between;
  align-items: baseline;
  
  margin: 10px;

  font-size: 36px;
  font-weight: bold;
  font-family: arial;
  text-align: center;
}

And we’ll add our new nav class to the css:

.nav {
  font-size: 18px;
  font-weight: bold;
  color: #e5a5a5;
  
  cursor: pointer;
}

Our app now looks like this:

A view of the pokedex app after adding nav buttons

All that is left is adding the onclick handlers to make the buttons work. The data returned in our API call has keys named "previous" and "next" which are populated with urls to the next page of results. We can add some onclick handlers to our new buttons with this information.

const getPokemonList = (url) => {
  while (container.firstChild) {
    container.removeChild(container.firstChild);
  }
  
  axios.get(url)
       .then( (response) => {
          const data = response.data.results;
          const prev = response.data.previous;
          const next = response.data.next;
    
          data.map( (pokemon) => {
            const div = document.createElement('div');
            const text = document.createTextNode(pokemon.name);
            
            div.appendChild(text);
            div.className += 'pokeList';
            container.appendChild(div);
          });
    
          if (prev) {
            document.getElementById('prev').onclick = () => {
              getPokemonList(prev);
            }
          }
    
          if (next) {
            document.getElementById('next').onclick = () => {
              getPokemonList(next);
            }
          }
       })
       .catch( (error) => {
          throw error;
       });
}

Now, if our API call returns a url for "previous" or "next" we will add an event listener to our new buttons which when clicked will call our getPokemonList function with the url to the appropriate page of results.

Creating Cards For Selected Pokemon

Looking at lists of pokemon names is fun. Super fun. With 20 results per page, and 811 total results, we can look at 41 pages of pokemon names. For some, this may be enough but I think we can take this a step further and display information about each pokemon on demand.

To accomplish this, we need to have some way of selecting which pokemon we want to look at. Each list item already looks like a button, let’s make it behave like one as well.

data.map( (pokemon) => {
  const div = document.createElement('div');
  const text = document.createTextNode(pokemon.name);
            
  div.appendChild(text);
  div.className += 'pokeList';
  div.onclick = () => { getPokemonDetails(pokemon.url); };
  container.appendChild(div);
});

In our Array.map() method we’ve added an onclick event handler to each list item. This event handler will take the url we received in each object and pass it to a new function which looks like this:

const getPokemonDetails = (url) => {
  while (container.firstChild) {
    container.removeChild(container.firstChild);
  }
  
  axios.get(url)
       .then( (response) => {
          
       })
       .catch( (error) => {
          throw error;
       });
}

Look familiar? The only thing that’s changed is the name! If we were following the D.R.Y. (Don’t Repeat Yourself) principle we’d just modify our existing function to handle our new data format we’ll receive, but for the sake of simplicity we will just create a new function to handle the api calls for pokemon details. They call this W.E.T. instead of DRY (We Enjoy Typing!)

If you want you can log the result of this call to your console, I will again go to Postman to see what we can expect.

A view of the results of our API call in Postman

Aaaaaaand it’s huge. Really huge. 9018 lines, in Postman. If you are more industrious than I am, you can go through all the information we have available here, but I am going to choose some data and we’ll ignore the rest.

Since we’re only dealing with one data set instead of an array of data, we don’t need to map through anything so this will be pretty simple. Into our call response we will add:

.then( (response) => {
  const data = response.data;
    
  const nameDiv = document.createElement('div');
          
  nameDiv.appendChild(
    document.createTextNode(data.species.name)
  );
  nameDiv.className = 'pokeName';
  container.appendChild(nameDiv);
    
  const img = document.createElement('img');
    
  img.src = data.sprites.front_default;
  img.className = 'pokePic';
  container.appendChild(img);
    
  const statsDiv = document.createElement('div');
    
  statsDiv.className = 'stats';
  statsDiv.appendChild(
    document.createTextNode("Height: " + data.height + " || Weight: " + data.weight)
  );
  container.appendChild(statsDiv);
})

This may seem a little complicated, but basically we’re just creating new html elements, assigning them some attributes like class or src and then adding them into our app.

Since we’re adding some classes here let’s go ahead and define them in our css by adding:

.pokeName {
  margin: 20px;
  
  font-size: 28px;
  font-weight: bold;
  text-align: center;
}

.pokePic {
  width: 250px;
}

.stats {
  font-size: 18px;
  font-weight: 600;
}

Now, when we click a name in our list we should see:

A view of the pokemon card

Not bad! As I said earlier, there is a lot of information returned for each pokemon so if you wanted to improve on the card you could add all kinds of cool information. For the purposes of this tutorial, I just want to add one more thing and make one small change.

While viewing a card in the pokedex it would be nice to be able to return to the list, so let’s add a button at the bottom of the card allowing us to do just that.

To make this work, we need to know where we’ve been, so we will add a second parameter to the getPokemonDetails function, which will allow us to pass the url of the list we were just viewing. Then we can set an onclick event handler which will reload the previous list. Our getPokemonDetails function should now look like this:

const getPokemonDetails = (url, listUrl) => {
  while (container.firstChild) {
    container.removeChild(container.firstChild);
  }
  
  axios.get(url)
       .then( (response) => {
          const data = response.data;
    
          const nameDiv = document.createElement('div');
          
          nameDiv.appendChild(
            document.createTextNode(data.species.name)
          );
          nameDiv.className = 'pokeName';
          container.appendChild(nameDiv);
    
          const img = document.createElement('img');
    
          img.src = data.sprites.front_default;
          img.className = 'pokePic';
          container.appendChild(img);
    
          const statsDiv = document.createElement('div');
    
          statsDiv.className = 'stats';
          statsDiv.appendChild(
            document.createTextNode("Height: " + data.height + " || Weight: " + data.weight)
          );
          container.appendChild(statsDiv);
    
          const button = document.createElement('div');
    
          button.className = 'back';
          button.appendChild(
            document.createTextNode("Back")
          );
          button.onclick = () => { getPokemonList(listUrl) };
          container.appendChild(button);
       })
       .catch( (error) => {
          throw error;
       });
}

Since this function is now expecting us to pass two parameters when it’s called, we also need to update the onclick event in our getPokemonList function so it sends everything we need.

div.onclick = () => { getPokemonDetails(pokemon.url, url); };

Now we can return to our list and browse other pokemon! Our finished card looks like this:

A view of the finalized card

The last thing I’d like to do is just clean up one little bit of UI/UX that is bugging me. Currently while viewing a card if we click the Prev or Next buttons it will load the previous or next list. What I’d like to do is just disable those buttons.

In our getPokemonDetails function let’s add something to do just that:

const getPokemonDetails = (url, listUrl) => {
  while (container.firstChild) {
    container.removeChild(container.firstChild);
  }
  
  document.getElementById('prev').onclick = () => {
    return false;
  }
  
  document.getElementById('next').onclick = () => {
    return false;
  }
  
  axios.get(url)

And there you have it. We’ve successfully made a whole lot of API calls and used the data to build an app, and in the process learned about promises and async tasks.

You can view the finished project here.

Making API Calls

This tutorial uses some ES6 syntax. You can learn about const here and arrow functions here.

I suggest coding along with this tutorial to get the most out of it. Writing code seems to cement concepts better than copy/pasting other people’s code.

The project we will be making is a simple Pokemon Pokedex, using the Pokeapi V2 API.

One thing I see many people struggle with is making API calls. I had a lot of trouble learning how to do this, and was surprised to find most suggested using jQuery to get the job done. jQuery has a few different tools to make calls with, but I still don’t believe it’s necessary to import a giant library for a tiny task.

What About A Tiny Library?

Now we’re talking. I’m not against using tools outside of vanilla JavaScript, and I do so often. My favorite for making API calls is Axios.

Axios describes itself as a “Promise based HTTP client for the browser and node.js.” Most of these terms beginners should understand, but the difficult concept here is “promise based.” It took me a while to understand promises, so let’s look at what happens when we make an API call and see if we can understand the process a little better.

Before we get to that, though, let’s get some of the code out of the way

For those of you following along in CodePen, please add the axios CDN into the JavaScript area of the settings menu. Any code depicted between <style> tags goes in the box labeled CSS, everything between the <body> tags goes in the html box, and everything in the <script> tags goes in the JavaScript box.

<!DOCTYPE html>
<html>
  <head>
    <meta charset='utf-8'>
    <script src='https://cdnjs.cloudflare.com/ajax/libs/axios/0.15.3/axios.min.js'></script>
    <style>
      html, body {
        margin: 0;
        padding: 0;
      }
      
      .container {
        display: flex;
        flex-direction: column;
        justify-content: center;
        align-items: center;
        
        width: 100vw;
        min-height: 100vh;
      }
      
      .main {
        width: 450px;
        height: 600px;
        
        background-color: #84152b;
      }
      
      .header {
        margin: 10px;
        
        font-size: 36px;
        font-weight: bold;
        font-family: arial;
        text-align: center;
      }
      
      .pokedex {
        margin: 10px;
        
        height: 520px;
        
        border: 1px solid black;
        background-color: #a39e9f;
      }
    </style>
    <script>

    </script>
  </head>
  <body>
    <div class='container'>
      <div class='main'>
        <div class='header'>Pokeapi Pokedex</div>
        <div class='pokedex'>
          
        </div>
      </div>
    </div>
  </body>
</html>

This gives us just a simple layout that looks like:
A view of the basic layout
As you can see, we’re not aiming for anything fancy here. We just need a place to display the information we get back from our API call.

Promises

API calls, along with many other methods we’ll use in web development, execute asynchronously. JavaScript is a single-threaded application, which means that when we write an algorithm it executes from the top to the bottom, in order. This gives us predictable behavior and makes it easy to understand the order in which our code will execute. Asynchronous (async) tasks get called in order as you’d expect, but they can take some time to complete so the rest of the code gets called while the async task finishes up behind the scenes. This makes sense, because we don’t want to just sit around and wait for an API call to complete before moving on with the rest of our code. Users are impatient.

The difficulty here is that if we don’t wait for the call to complete, the information returned will not be available when the rest of the script executes. This can lead to a lot of frustration for a developer because when we look at it in our code it all makes sense. Make this API call, put the results here, here and here. The problem is the call hasn’t finished before we try to use the results.

This is where the magic that is promises comes into play. In the simplest terms, a promise is like an event listener. We’ve used those before, where we tell the app to listen for a button being clicked or a key to be pressed, and then execute a bit of code in response. Promises work in much the same way, but the event we will be ‘listening’ for here is the API call to complete. Then it will execute a bit of code for us, using the results of the API call.

Let’s go ahead and make a call to our Pokeapi so we can see what kind of information it will return, then we can figure out how we want to display it in our app. I like to use a Chrome app called Postman to play around with APIs, this allows us to easily try out the necessary query strings and API parameters before adding them into our app. I find it’s much easier than making a series of console.log() statements.

Using https://pokeapi.co/api/v2/pokemon as our api url in Postman, we get back an object that looks like this:

A view of our api call in Postman

{
  "count": 811,
  "previous": null,
  "results": [
    {
      "url": "http://pokeapi.co/api/v2/pokemon/1/",
      "name": "bulbasaur"
    },
    {
      "url": "http://pokeapi.co/api/v2/pokemon/2/",
      "name": "ivysaur"
    },
    {
      "url": "http://pokeapi.co/api/v2/pokemon/3/",
      "name": "venusaur"
    },
    {
      "url": "http://pokeapi.co/api/v2/pokemon/4/",
      "name": "charmander"
    },
    {
      "url": "http://pokeapi.co/api/v2/pokemon/5/",
      "name": "charmeleon"
    }
  ...
}

I forgot to update the api url to https instead of http for the images above, but calling https://pokiapi.co/api/v2/pokemon will return the above objects with all of the urls updated to https

We have a key for “count” showing us how many pokemon have been returned, and the meat of our data, a key called “results,” an array of objects representing the first 20 of 811 pokemon this API has returned to us. Arrays are easy to iterate through, so this is good news. Let’s go ahead and add our API call using axios to our app with the following code:

<script>
const container = document.querySelector('.pokedex');

const getPokemonList = () => {
  const url = "https://pokeapi.co/api/v2/pokemon";
  
  axios.get(url)
       .then( (response) => {
          console.log(response);
       })
       .catch( (error) => {
          throw error;
       });
}

getPokemonList();
</script>

This is all it takes to use axios. We pass the API url into axios, and chain onto the initial call with .then(). This is the promise. This tells our app to listen for that API call to complete, and execute whatever code we’ve put in our .then() function. In this case, for right now we are logging the API response to the console so we can see what our response will look like. Also, we have a .catch() statement added to handle any errors our API call may generate.

A view of the dev console after making initial api call

This looks a lot different that what we saw in Postman, it’s a good thing we checked before trying to use the data. It looks like Axios must build a default response object with header information, and the information we want is stored in the key labeled “data.results” Let’s update our script a little and run it again to be sure.

<script>
const container = document.querySelector('.pokedex');

const getPokemonList = () => {
  const url = "https://pokeapi.co/api/v2/pokemon";
  
  axios.get(url)
       .then( (response) => {
          console.log(response.data.results);
       })
       .catch( (error) => {
          throw error;
       });
}

getPokemonList();
</script>

This results in:

An updated view of the api call results logged in the dev console

That’s much better. We’ve trimmed it down to just the array of objects representing the different Pokemon. Now we can loop through this array and add the information to our display. Let’s add some style for our list of pokemon, then we’ll change our axios call to handle the data we receive.

.pokeList {
  width: 90%;
  
  margin: 3px;
  
  font-size: 16px;
  font-weight: bold;
  color: #160105;
  text-align: center;
  
  border: 1px solid black;
  
  cursor: pointer;
}

And our updated axios call:

const getPokemonList = () => {
  const url = "https://pokeapi.co/api/v2/pokemon";
  
  axios.get(url)
       .then( (response) => {
          const data = response.data.results;
    
          data.map( (pokemon) => {
            const div = document.createElement('div');
            const text = document.createTextNode(pokemon.name);
            
            div.appendChild(text);
            div.className += 'pokeList';
            container.appendChild(div);
          });      
       })
       .catch( (error) => {
          throw error;
       });
}

We take the response from our axios call and assign the information stored in the data.results key to a variable named data. Since this information is an array, we can use the method Array.map() to iterate through the array and generate our list of pokemon dynamically. For each item of the array we create a new <div> element, set the text of the element to the name of the pokemon and add our new class we created in the css section. This repeats for every item in the array, giving us the following result:

A view of our pokedex app with api data added

Next week we’ll finish this up by adding pagination to the list, allowing us to view the remaining pokemon in the database, as well as adding functionality which will allow us to view the stats of each pokemon.