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 start
and 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.html
with 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.ts
for 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):
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
<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> |
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.

Add the following initial content to the heroes.spec.ts:
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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>' | |
}); | |
}); | |
}); | |
}); |
and the heroes.tsx:
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
import { Component } from '@stencil/core'; | |
@Component({ | |
tag: 'app-heroes', | |
styleUrl: 'heroes.css' | |
}) | |
export class Heroes { | |
render() { | |
return ( | |
<div> | |
</div> | |
); | |
} | |
} |
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.
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
<div> | |
{this.hero} | |
</div> |
Show the Heroes view
To display the Heroes
component, you can add it to the template in app-home.tsx
.
Remember that app-heroes
is the element selector for the Heroes
component. So add an <app-heroes>
element to the app-home.tsx
JSX template, just below the app-home div.
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
<div class='app-home'> | |
<app-heroes></app-heroes> | |
<stencil-route-link url='/profile/stencil'> | |
<button> | |
Profile page | |
</button> | |
</stencil-route-link> | |
</div> |
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.
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
export class Hero { | |
id: number; | |
name: string; | |
} |
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:
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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> | |
); | |
} | |
} |
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:
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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> | |
); | |
} | |
} |
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/
handleChangeName(event) {
this.hero.name = event.target.value;
this.hero = { …this.hero };
}
LikeLike