Note: I'm starting off at the end of Part 1.
Sites would be very boring if the information on the site stayed the same every time it loaded. One very common method of being dynamic is to load the data using an API call. This is where a web site requests information from a service and receives data via a API call using JavaScript. The API call can be at the time the web site first loads or can be based on options that the user will select. For instance on a sports site, the user may select their favorite team and the API call would then ask for just that team's information.
The plan here is to create a component to hold the JavaScript that with be used to make the API call. This way any other component that needs to make an API call well use this single component. This component is going to use a lot of ES6 features. The two that this component will rely on is fetch and promise.
fetch :: Fetch is a JavaScript method of making an API call. It's cleaner, simpler and more powerful to use then the older `XMLHttpRequest` function. The older function is still available in case an API source requires it's use.
promise :: A promise is a proxy for a value that may not be known at the time the promise is created. Fetch returns a promise when it's first called. It will be in one of three states; pending, fulfilled, rejected. The advantage is that we can have the app continue to function until the promise is fulfilled.
Components that serve up data are normally called a store. Svelte has built in a store functionality. The store we'll be creating won't be using the Svelte features. Stores come in different functions. In this case the store will be read-only. When called, the store will use an API call to provide data back to whatever called the store. It's possible to have multiple stores in the same app.
StarWars.js
In the src folder, create a folder called store.
Inside the src/store folder create a fold called StarWars.js
. Notice for this we're not using the Svelte extension, that's because this isn't a Svelte component. We won't have any CSS or HTML, just JavaScript. We won't need the script tags either. But that does mean we'll need to export the JavaScript that will be the store. What we are building is an ES6 JavaScript Module.
In the file place:
const CATEGORY = {
"films": "https://swapi.dev/api/films/",
"people": "https://swapi.dev/api/people/",
"planets": "https://swapi.dev/api/planets/",
"species": "https://swapi.dev/api/species/",
"starships": "https://swapi.dev/api/starships/",
"vehicles": "https://swapi.dev/api/vehicles/"
};
const StarWarsStore = {
getAllPersons: () => {
let apicall = fetch(CATEGORY.people)
.then(function(response) {
if (!response.ok) {
throw new Error(`HTTP error! status: ${response.status}`);
}
return response.json();
});
return apicall;
}
};
export default StarWarsStore;
Ok, that's a lot. Let's break it down.
The const CATEGORY
is an object of the API end points that Star Wars API has available. I find it easier to extend the app if the data I'm using is listed in a object that I can swap out quickly. The endpoints could be hard coded directly into the fetch statements.
It's my preference to place these type of "magic" strings and numbers in at the start of functions or apps. It's more typing but it makes extending, debugging and maintaining the code much easier.
The workhorse here is StarWarsStore
. The export statement at the bottom is showing that only StarWarsStore
will being exported. CATEGORY
is used only inside this component and is not available to other components. Right now StarWarsStore
has only one function, getAllPersons
. We'll see how to use it when we use it in a different component.
Break down of getAllPersons
In the first line of the getAllPersons
function, we're calling the fetch function and passing in the API endpoint for all people. Fetch returns a promise which will be stored in the apicall variable.
let apicall = fetch(CATEGORY.people)
After fetch finishes, either fulfilled or rejected, we've linked a .then
. .then
is a way that promises can process data in stages. The function that .then
calls, will receive the response from the fetch. This daisy chain can go on as many times as needed.
.then(function(response) {
In the response we're checking if the response is not ok !response.ok
.
if (!response.ok) {
throw new Error(`HTTP error! status: ${response.status}`);
}
The function is returning the json() portion of the response.
return response.json();
});
And last but not least, we need to return the apicall, as promised to whatever has called the getAllPersons
function.
return apicall;
}
Using the store
You've probably already guessed the first step, we need to add this to a component. So which component? So let's create one.
In the components
folder, create a file called PersonList.svelte
. We'll use this to display the list of characters we'll be getting from getAllPersons
.
Copy this into PersonList.svelte
.
<script>
import StarWarsStore from '../store/StarWars';
let allPersons = StarWarsStore.getAllPersons();
</script>
<div class="personList">
{#await allPersons}
<p>Loading......</p>
{:then persons}
{JSON.stringify(persons)}
{:catch error}
<p>Whoops, there was an error! {JSON.stringify(error)} </p>
{/await}
</div>
<style></style>
The script portion does two things; import the StarWarsStore and run the getAllPersons
function.
As we said earlier, only the function getAllPersons
has been exported. On the next line we are executing the function and putting it into the variable allPersons.
What do you think is in the allPersons variable? If you said a promise, you are correct. The API call was made but we don't know how long it'll take to be returned. The idea is to wait until the promise has been fulfilled. And then it'll be available.
How would we do handle this without Svelte? We'd use the .then()
portion of a promise. We can chain as many .then()
as we need. Using a function to pass to the .then()
that would use the response and output it to the screen. Or whatever we needed to happen when the response was fulfilled.
But with Svelte? Just tell Svelte what you want to happen to each state of the promise:
In the HTML portion break down the three states:
{#await __promise__}
<p>We put in whatever we want while the promise is pending.</p>
{:then __response__}
<p>Once fulfilled, the response will be passed in with the name provided and we can display it.</p>
{:catch __error__}
<p>If rejected, we'll describe something to do with the error.</p>
{/await}
App.svelte
In the App.svelte
file add the following to the script section.
import PersonList from './components/PersonList.svelte';
In the HTML section add
<PersonList />
between the Header
and Footer
.
These steps will place our newly created PersonList
in the proper spot.
Understanding the response
At this the app will run and display the response to the screen. Because we are using JSON.stringify(persons)
, the response is a long string. Not very pretty at all. A reason I choose to use this API is that the documentation is very good. If you go to https://swapi.dev/api/people/ you'll find that Star Wars API site has the response very nicely laid out.
From there we can see that the response object has 4 key - value pairs.
- count : count of items available in the database
- next : the url to get the next set of items
- previous : the url to get the previous set of items
- results : an array of items in the result
What's this about next and previous? In this API there are only 82 characters. But imagine if you were hitting the API for a weather site. And the API feed contained all the cities in the United States. That would be thousands. APIs normally don't want to provide that large of a package. The normal process is to page through the data. If you look through the response, there is ten items. Ten characters we're being sent.
We can change the {:then}
portion to read this:
{:then persons}
<p>{JSON.stringify(persons.count)}</p>
<p>{JSON.stringify(persons.next)}</p>
<p>{JSON.stringify(persons.previous)}</p>
<p>{JSON.stringify(persons.results)}</p>
<p>{JSON.stringify(persons.results[0])}</p>
{:catch error}
Now it's a little easier to see.
- the count is 82
- the next URL is "swapi.dev/api/people/?page=2"
- the previous URL is null
The last one breaks out the first character; Luke Skywalker.
Now we have a good idea of what the API is providing us. And we have a way of getting to the information.
Next Up
We're going to add a component that displays a single person and we'll see how to pass the person down to the component. We'll loop through the results array and pass each to the Person
component.
Please note that I've uploaded the code to a GitHub repo. I will upload a post where I talk about getting into the repo and looking at each post's code.
Tell next time....