Stencil — Tutorial: Tour of Heroes Part 5

Time to make your final decision!

If you just landed on this tutorial make sure to read part 1 to part 4 over here first: Stencil — Tutorial: Tour of Heroes Part 1

If you have not done part 4 yet, you can pick up the code from here to follow along in this part 5 of the tutorial: https://github.com/nerdic-coder/stencil-tour-of-heroes/tree/v4.0

HTTP Requests

Now it’s time to take the step from mock data to fetching the heroes from a backend server. It could anything from firebase, mongodb, php server or any system you can think that can deliver some JSON-data.

Json-server

For this tutorial it will be enough with a simple json-server that I found on npmjs.com, so start by installing the json server as a developer dependency with command:

npm install json-server --save-dev

Then create a json file db.json in the root folder, that will be used as the database for the json-server, we will populate it with our mocked heroes list as a starting point:


{
"heroes": [
{
"id": 11,
"name": "Mr. Nice"
},
{
"id": 12,
"name": "Narcos"
},
{
"id": 13,
"name": "Bombasto coolio"
},
{
"id": 14,
"name": "Celeritas"
},
{
"id": 15,
"name": "Magneta"
},
{
"id": 16,
"name": "RubberMan"
},
{
"id": 17,
"name": "Dynama"
},
{
"id": 18,
"name": "Dr IQ"
},
{
"id": 19,
"name": "Magma"
},
{
"id": 20,
"name": "Tornado"
},
{
"id": 25,
"name": "Kalle"
}
]
}

view raw

db.json

hosted with ❤ by GitHub

Then we will create a new npm script in the package.json to make a shorter command to start the json-server, add the following to the scripts section of package.json :

"mock": "json-server --watch db.json"

Then we can start it with command npm run mock . So now we just need to rewrite the app to use this http service instead of the mock file.

Server configuration

Since it’s a bad idea to have the server url hard coded in all the methods calling the server, we will create a simple config file containing the server url. This file can also be used for other global configurations in the future.

Create a new folder under src called global and the create a file config.ts with the following content:


export const CONFIG: Config =
{ SERVER_URL: 'http://localhost:3000/' };
class Config {
SERVER_URL: string;
}

view raw

config.ts

hosted with ❤ by GitHub

Now if the server url changes for some reason, we will only need to update it in one place.

Get heroes with a XMLHttpRequest

Since we don’t have Angular’s fancy HttpClient here we will use the old school Javascript standard XMLHttpRequest object.

So the first thing we need to do in the HeroService class is importning the CONFIG like this:

import { CONFIG } from '../global/config';

Then we rewrite the getHeroes method like this:


getHeroes(): Observable<Hero[]> {
return Observable.create((observer) => {
const xhr = new XMLHttpRequest();
xhr.open('GET', CONFIG.SERVER_URL + 'heroes');
xhr.onload = () => {
if (xhr.status === 200) {
this.messageService.add(`HeroService: fetched heroes`);
observer.next(JSON.parse(xhr.responseText));
}
else {
observer.error(xhr.response);
}
};
xhr.send();
});
}

So first we create an Observable that will return the Heroes when the response from the server is processed.

The lines after that we setup the configuration for the XMLHttpRequest instance xhr with the server url and the path to the heroes on the server, we are using http method GET so the server knows we want a list of heroes returned.

Then we send the request with xhr.send(); and wait for the response. Then we process the response inside xhr.onload .

If the request is successful we set the returned hero list to the observable so that all subscribers know that a new list of heroes have been fetched from the backend. If an error occurs the observer returns an error.

That’s all we need to change, the rest of the application continuous to operate as before.

Get hero by id

Now we also want to change the getHero method in HeroService to get the hero from the API.

Change the getHero method like this:


getHero(id: number): Observable<Hero> {
return Observable.create((observer) => {
const xhr = new XMLHttpRequest();
xhr.open('GET', CONFIG.SERVER_URL + `heroes/${id}`);
xhr.onload = () => {
if (xhr.status === 200) {
this.messageService.add(`HeroService: fetched hero with id:${id}`);
observer.next(JSON.parse(xhr.responseText));
}
else {
observer.error(xhr.response);
}
};
xhr.send();
});
}

view raw

getHeroHttp.ts

hosted with ❤ by GitHub

It looks very similar to getHeroes method but here we add an id to the API path to only get one hero from the API.

Update heroes

Now we’re going into the more interesting part with having a backend service, updating heroes information.

So now we will create a updateHero method in the HeroService class like this:


/** PUT: update the hero on the server */
updateHero(hero: Hero): Observable<any> {
return Observable.create((observer) => {
const xhr = new XMLHttpRequest();
xhr.open('PUT', CONFIG.SERVER_URL + `heroes/${hero.id}`, true);
xhr.setRequestHeader('Content-type','application/json; charset=utf-8');
xhr.onload = () => {
if (xhr.status === 200) {
this.messageService.add(`HeroService: updated hero with id:${hero.id}`);
observer.next(JSON.parse(xhr.responseText));
}
else {
observer.error(xhr.response);
}
};
xhr.send(JSON.stringify(hero));
});
}

view raw

updateHero.ts

hosted with ❤ by GitHub

Looks very similar to the other once but here we send in a hero object that is to be updated. We use HTTP method PUT instead of GET to inform the server that we want to update an hero, we set a request header with a Content-type ‘application/json; charset=utf-8’, so the server knows what format we are sending to it.

Now let’s fix so that the hero-details component can actually save the changes on a hero.

Let’s start with adding a save method like this:


save(): void {
this.heroService.updateHero(this.hero)
.subscribe(() => this.goBack());
}

view raw

saveHero.ts

hosted with ❤ by GitHub

This method will take the current state of the hero object in hero-details and send it to our newly created updateHero method in HeroService and then it will leave the hero-details view.

Now all we have to do is add a button that will trigger the save method:

<button onClick={() => this.save()}>save</button>

Now we can update heroes on the hero-details view!

Add a new hero

Let’s create a form to add new heroes, we will begin with adding a new addHero method in the HeroService class like this:


/** POST: add a new hero to the server */
addHero (hero: Hero): Observable<Hero> {
return Observable.create((observer) => {
const xhr = new XMLHttpRequest();
xhr.open('POST', CONFIG.SERVER_URL + `heroes`, true);
xhr.setRequestHeader('Content-type','application/json; charset=utf-8');
xhr.onload = () => {
if (xhr.status === 201) {
this.messageService.add(`HeroService: added new hero`);
observer.next(JSON.parse(xhr.responseText));
}
else {
observer.error(xhr.response);
}
};
xhr.send(JSON.stringify(hero));
});
}

Again looks very similar to the updateHero method but now we make a POST requst instead telling the server to create a new hero instead of updating an existing hero.

Next step is to add a form to the heroes component that will be used for creating new heroes.

We will take the handleChangeName method that helps us keeping the hero object up to date and then we will add an add method that will save the new hero for us, like this:


handleChangeName(event) {
this.hero = {
id: this.hero.id,
name: event.target.value
};
}
add(): void {
this.hero.name = this.hero.name.trim();
if (!this.hero.name) { return; }
this.heroService.addHero(this.hero).subscribe(hero => {
this.heroes.push(hero);
this.hero = {
id: this.hero.id,
name: ""
};
});
}

view raw

addHero.ts

hosted with ❤ by GitHub

Nothing mind blowing going on here, the hero is sent to the service and then pushed into the heroes list once it’s saved.

Now let’s add the html form under the hero list like this:


<div>
<label>Hero name:
<input type="text" value={this.hero.name} onInput={(event) => this.handleChangeName(event)} placeholder="name" />
</label>
<button onClick={() => this.add()}>
add
</button>
</div>

Delete a hero

It might get crowded in your hero list while trying all this out, so it’s a good idea at this point to create a delete button in the heroes list.

Let’s add a deleteHero method to the HeroService like this:


/** DELETE: delete the hero from the server */
deleteHero (hero: Hero | number): Observable<Hero> {
return Observable.create((observer) => {
const id = typeof hero === 'number' ? hero : hero.id;
const xhr = new XMLHttpRequest();
xhr.open('DELETE', CONFIG.SERVER_URL + `heroes/${id}`, true);
xhr.onload = () => {
if (xhr.status === 200) {
this.messageService.add(`HeroService: deleted hero with id:${id}`);
observer.next(JSON.parse(xhr.responseText));
}
else {
observer.error(xhr.response);
}
};
xhr.send();
});
}

Again we setup the request and sends in the hero id but this time we send http DELETE request to remove the hero from the server.

Then let’s add a delete button on the hero list next to each hero:

<button class="delete" title="delete hero" onClick={() => this.delete(hero)}>x</button>

Let’s create the delete method that will glue this all together in the heroes.tsx file:

We begin with filtering out the hero from the heroes list, so we don’t have to reload the whole heroes list. Then we delete the hero from the server.

Search by name

The last thing we will add is a search method for our heroes.

Start with adding the search method in the HeroService.tsx file:

Similar request as the others, but now we also add a search term to the end of the request ‘heroes/?q=${term}‘.

Let’s create a new ‘HeroSearch’ component so we then will be able to insert a search form wherever we like later on.

Create the following file inside ‘/src/components/hero-search’:

  • hero-search.css
  • hero-search.spec.ts
  • hero-search.tsx

With the following contents:

So the core part here is the ‘private searchTerms = new Subject<string>();’, it’s an observable type called Subject that is wired to the search-box input.

Then in the search(event) method we update the searchTerm that then trigger the subscription we configured in the componentWillLoad() method. That updates the heroes list if all criteria in the configuration are meet.

Finally add the HeroSearch component to the end of dasboard.tsx ‘<app-hero-search></app-hero-search>’.

Conclusion

You have now reach the end of this series of converting the Angular Tour of Heroes to Stencil.

You can review the final solution on GitHub here: https://github.com/nerdic-coder/stencil-tour-of-heroes/tree/v5.0

Hope you have enjoyed this journey and please let me know in the comments what you think of this articles.

Please share this with a nerdic friend who want to learn more about Stencil.

Advertisement

One thought on “Stencil — Tutorial: Tour of Heroes Part 5

Add yours

Leave a Reply

Fill in your details below or click an icon to log in:

WordPress.com Logo

You are commenting using your WordPress.com account. Log Out /  Change )

Twitter picture

You are commenting using your Twitter account. Log Out /  Change )

Facebook photo

You are commenting using your Facebook account. Log Out /  Change )

Connecting to %s

This site uses Akismet to reduce spam. Learn how your comment data is processed.

Create a website or blog at WordPress.com

Up ↑

%d bloggers like this: