Stencil — Tutorial: Tour of Heroes

This story is about my attempt to convert the Angular’s popular Tutorial: Tour of Heroes into a Stencil application. I recommend that you read the introduction part of the Angular tutorial to get a feeling about what kind of app we will build here.

What you will have learned after this tutorial is how to build the Tour of Heros application with Stencil.

The Application Shell

Install the Stencil starter app

Start with cloning down the stencil app starter and install all dependencies.

git clone https://github.com/ionic-team/stencil-app-starter tour-of-heroes
cd tour-of-heroes
git remote rm origin
npm install

Then run npm startand the app should show up in a browser window.

The npm start command builds and start your app in development mode and reloads every time you edit a file.

Stencil components

Stencil apps is built up as web components that is written in JSX and Typescript that then is compiled into standard web components. The components is divided into folders most commonly under src/components/.

Change the application title

Open the project in your editor and navigate to the src/components/my-app/ folder.

The my-app component is the main component of the app since it’s included in the file src/index.htmlwith a custom html tag <my-app></my-app>.

In the my-app component folder you will find 3 files, my-app.css for the components private style sheets, my-app.spec.tsfor unit testing and my-app.tsx containing the components logic and JSX markup.

Open the file my-app.tsx and start by adding a private ‘title’ property to the MyApp class:

export class MyApp {
  private title: string = ‘Tour of Heroes’;
  ...

Then edit the h1 tag to contain {this.title}, save the file and you will notice the change in the browser within a few seconds.
Bonus: Update the title tag in the index.html to “Tour of Heroes” as well.

Add application styles

The Stencil starter app have the application wide styling in a style tag inside the index.html file, so let’s replace that with the Tour of Heros sample stylesheet like this (slightly modified to not look like crap):


<style>
/* Application-wide Styles */
h1 {
font-family: Arial, Helvetica, sans-serif;
font-size: 250%;
}
h2, h3 {
color: #444;
font-family: Arial, Helvetica, sans-serif;
font-weight: lighter;
}
body {
margin: 2em;
margin: 0px;
padding: 0px;
font-family: sans-serif;
}
body, input[text], button {
font-family: Cambria, Georgia;
}
/* everywhere else */
* {
font-family: Arial, Helvetica, sans-serif;
}
</style>

view raw

herostyle.html

hosted with ❤ by GitHub

You can click here to review what we have done so far. (Be kind and give my Github project a star ⭐️😉).

The Hero Editor

Now we will create a stencil component to display information about a hero.
Since Stencil doesn’t have any CLI (Command Line Interface) yet, we will have to create the new component manually.

So create a ‘heroes’ folder in src/components/ and create the 3 files heroes.css, heroes.spec.ts and heroes.tsx inside that folder.

File structure after creating the heroes component.

Add the following initial content to the heroes.spec.ts:


import { render } from '@stencil/core/testing';
import { Heroes } from './heroes';
describe('app-heroes', () => {
it('should build', () => {
expect(new Heroes()).toBeTruthy();
});
describe('rendering', () => {
beforeEach(async () => {
await render({
components: [Heroes],
html: '<app-heroes></app-heroes>'
});
});
});
});

view raw

heroes.spec.ts

hosted with ❤ by GitHub

and the heroes.tsx:


import { Component } from '@stencil/core';
@Component({
tag: 'app-heroes',
styleUrl: 'heroes.css'
})
export class Heroes {
render() {
return (
<div>
</div>
);
}
}

view raw

heroes.tsx

hosted with ❤ by GitHub

You always import the Component symbol from the Stencil core library and annotate the component class with @Component .

The component have two metadata properties,
* tag, the name of the html tag that is used to include this component in any other html page/component.
* styleUrl, the url to the components private styling.

Add a hero property

Add a hero property to the Heroes component for a hero named “Windstorm.”

private hero:string = 'Windstorm';

Show the hero

Then print out the hero in the html part inside the div element.


<div>
{this.hero}
</div>

Show the Heroes view

To display the Heroescomponent, you can add it to the template in app-home.tsx.

Remember that app-heroes is the element selector for the Heroescomponent. So add an <app-heroes> element to the app-home.tsx JSX template, just below the app-home div.


<div class='app-home'>
<app-heroes></app-heroes>
<stencil-route-link url='/profile/stencil'>
<button>
Profile page
</button>
</stencil-route-link>
</div>

view raw

app-home.tsx

hosted with ❤ by GitHub

Create a Hero class

A real hero is more than a name.

Stencil can consume regular Typescript classes just like Angular.
Create a Hero class in its own file in a new src/models folder. Give it id and name properties.


export class Hero {
id: number;
name: string;
}

view raw

hero.ts

hosted with ❤ by GitHub

Return to the Heroes component and import the Hero class.

Refactor the component’s hero property to be of type Hero. Initialize it with an id of 1 and the name Windstorm.

The revised Heroes component file should look like this:


import { Component } from '@stencil/core';
import { Hero } from '../../models/hero';
@Component({
tag: 'app-heroes',
styleUrl: 'heroes.css'
})
export class Heroes {
private hero:Hero = {
id: 1,
name: 'Windstorm'
};
render() {
return (
<div>
<h2>{ this.hero.name } Details</h2>
<div><span>id: </span>{this.hero.id}</div>
<div><span>name: </span>{this.hero.name}</div>
</div>
);
}
}

view raw

heroes.tsx

hosted with ❤ by GitHub

Format with an Uppercase function

Since Stencil don’t have anything similar to Angular’s pipes, we will instead use the standard JavaScript function toUpperCase() .

Modify the this.hero.name binding like this:

{ this.hero.name.toUpperCase() }

Before we continue let’s remove the app-home and app-profile components.

  • Delete the app-home and app-profile component folder.
  • Remove the app-profile stencil-route from my-app.tsx .
  • Change the other stencil route from app-home to app-heroes.

You need to import the stencil router in the my-app.tsx for the routing to continue working:

import { } from '@stencil/router';

Then one last detail, add the following styling to the heroes.css :

.app-heroes {
  padding: 10px;
}

and add the class app-heroes to the top div element in heroes.tsx .

If you want to review what we’ve done so far check out the first tag of this project on Github here: https://github.com/nerdic-coder/stencil-tour-of-heroes/tree/v1.0

Edit the hero

Users should be able to edit the hero name in an <input> textbox.

The textbox should both display the hero’s name property and update that property as the user types. That means data flow from the component class out to the screen and from the screen back to the class.

To automate that data flow, setup a two-way data binding between the <input> form element and the this.hero.name property.

Refactor the details area in the Heroes component so it looks like this:


import { Component, State } from '@stencil/core';
import { Hero } from '../../models/hero';
@Component({
tag: 'app-heroes',
styleUrl: 'heroes.css'
})
export class Heroes {
@State() private hero:Hero = {
id: 1,
name: 'Windstorm'
};
handleChangeName(event) {
this.hero = {
id: this.hero.id,
name: event.target.value
};
}
render() {
return (
<div class='app-heroes'>
<h2>{ this.hero.name.toUpperCase() } Details</h2>
<div><span>id: </span>{this.hero.id}</div>
<div>
<label>name:
<input type="text" value={this.hero.name} onInput={(event) => this.handleChangeName(event)} placeholder="name" />
</label>
</div>
</div>
);
}
}

view raw

heroes.tsx

hosted with ❤ by GitHub

Since Stencil don’t have anything similar to Angular’s ngModel we will have to write the two way binding flow yourself.

So first to make the hero object issue a re-rendering to changes we need to add the State decorator to it.

Then we also need a function to update the heroes name, take a look at the handleChangeName(event) function above how we update the name for the object. The re-rendering would not occur if we would only update the heroes name directly with this.hero.name = event.target.value . The change is only noticed when the whole hero object is reassigned.

The last part is the input field that gets the heroes name as the value and is defining an event ‘onInput’ that is triggering our function every time the input is changed.

If someone knows any other ways to have two-way binding in Stencil, please let me know in the comments below.

Conclusion

We have reached the end of the first part in this tutorial, stay tuned for the second part where we will create a heroes list and making the heroes app more useful.

You can review the code for this tutorial on Github at: https://github.com/nerdic-coder/stencil-tour-of-heroes/tree/v1.1

Continue with part 2 here: https://nerdic-coder.com/2018/04/25/stencil-tutorial-tour-of-heroes-part-2/

Advertisement

3 thoughts on “Stencil — Tutorial: Tour of Heroes

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: