Stencil — Tutorial: Tour of Heroes Part 4

When people assumes that you built the project with Angular.

If you just landed on this tutorial make sure to read part 1 to part 3 over here first: https://nerdic-coder.com/2018/04/21/stencil-tutorial-tour-of-heroes/

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

Routing

Now we will split up the heroes list and heroes details into two separate pages, we will also create a new dashboard view.

We will be using the stencil router to navigate between the pages.

Add routes

Our app already have a stencil router since before in the my-app.tsx , but it’s only been loading one page up until now.


<main>
<stencil-router>
<stencil-route url='/' component='app-heroes' exact={true}>
</stencil-route>
</stencil-router>
</main>

Add a navigation link (stencil-route-link)

Add a

element and, within that, an element that, when clicked, triggers navigation to the Heroes component.


<nav>
<stencil-route-link url="/dashboard">Dashboard</stencil-route-link>
<stencil-route-link url="/heroes">Heroes</stencil-route-link>
</nav>

view raw

nav.html

hosted with ❤ by GitHub

Add a dashboard view

Create a new folder dashboard inside the components folder and create the following files in the new folder,

  • dashboard.css
  • dashboard.spec.ts
  • dashboard.tsx

Replace the default file content in these three files as follows and then return for a little discussion:


/* DashboardComponent's private CSS styles */
[class*='col-'] {
float: left;
padding-right: 20px;
padding-bottom: 20px;
}
[class*='col-']:last-of-type {
padding-right: 0;
}
a {
text-decoration: none;
}
*, *:after, *:before {
-webkit-box-sizing: border-box;
-moz-box-sizing: border-box;
box-sizing: border-box;
}
h3 {
text-align: center; margin-bottom: 0;
}
h4 {
position: relative;
}
.grid {
margin: 0;
}
.col-1-4 {
width: 25%;
}
.module {
padding: 20px;
text-align: center;
color: #eee;
max-height: 120px;
min-width: 120px;
background-color: #607D8B;
border-radius: 2px;
}
.module:hover {
background-color: #EEE;
cursor: pointer;
color: #607d8b;
}
.grid-pad {
padding: 10px 0;
}
.grid-pad > [class*='col-']:last-of-type {
padding-right: 20px;
}
@media (max-width: 600px) {
.module {
font-size: 10px;
max-height: 75px; }
}
@media (max-width: 1024px) {
.grid {
margin: 0;
}
.module {
min-width: 60px;
}
}

view raw

dashboard.css

hosted with ❤ by GitHub


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


import { Component, State } from '@stencil/core';
import { Hero } from '../../models/hero';
import { HeroService } from '../../services/hero.service';
@Component({
tag: 'app-dashboard',
styleUrl: 'dashboard.css'
})
export class Dashboard {
private heroService: HeroService;
@State() private heroes: Hero[];
constructor() {
this.heroService = HeroService.Instance;
}
componentWillLoad() {
this.getHeroes();
}
getHeroes(): void {
this.heroService.getHeroes()
.subscribe(heroes => this.heroes = heroes.slice(1, 5));
}
render() {
return (
<div class='app-dashboard'>
<h2>Top Heroes</h2>
<div class="grid grid-pad">
{this.heroes ? (this.heroes.map((hero) =>
<a class="col-1-4">
<div class="module hero">
<h4>{hero.name}</h4>
</div>
</a>
)) : (null)}
</div>
</div>
);
}
}

view raw

dashboard.tsx

hosted with ❤ by GitHub

The template presents a grid of hero name links.

  • The map repeater creates as many links as are in the component’s heroes array.
  • The links are styled as colored blocks by the dashboard.css.
  • The links don’t go anywhere yet but they will shortly.

This getHeroes reduces the number of heroes displayed to four (2nd, 3rd, 4th, and 5th).

Add the dashboard route

Add the following routes to the stencil-router in my-apps.tsx file:


This makes the dashboard the default page and also availabe on path /dashboard .

Navigating to hero details

Now we will make hero details into it’s own page, start with deleting the hero details from the heroes.tsx file.

Then add a hero details stencil route in my-apps.tsx :


The “:id” part defines that a parameter with a hero id will be at that position in the url.

Then we’ll add details links to the heroes in the dashboard with the “hero.id” as the id parameter,

We also add a similar link in the heroes list,

Extract the id route parameter

Now in the hero-details.tsx we need to catch the id from thr route parameter, we do this with the MatchResults from the stencil router package.

The end result will look something like this:


import { Component, Prop } from '@stencil/core';
import { MatchResults } from '@stencil/router';
import { Hero } from '../../models/hero';
import { HeroService } from '../../services/hero.service';
@Component({
tag: 'app-hero-details',
styleUrl: 'hero-details.css'
})
export class HeroDetails {
private heroService: HeroService;
@Prop() match: MatchResults;
@Prop({ mutable: true }) private hero: Hero;
constructor() {
this.heroService = HeroService.Instance;
}
componentWillLoad() {
this.getHero();
}
getHero() {
this.heroService.getHero(parseInt(this.match.params.id))
.subscribe(hero => {
this.hero = hero;
});
}
handleChangeName(event) {
this.hero = {
id: this.hero.id,
name: event.target.value
};
}
render() {
return (
<div class='app-hero-details'>
{this.hero ? (
<div>
<h2>{ this.hero.name.toUpperCase() } Details</h2>
<div><span>ids: </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>
) : (
null
)
}
</div>
);
}
}

First we create a variable from the MatchResults then we will read the id value like this this.match.params.idin the newly created method getHero() .

Add HeroService.getHero()

Open HeroService and add this getHero() method:


getHero(id: number): Observable<Hero> {
// TODO: send the message _after_ fetching the hero
this.messageService.add(`HeroService: fetched hero id=${id}`);
return of(HEROES.find(hero => hero.id === id));
}

Find the way back

By clicking the browser’s back button, you can go back to the hero list or dashboard view, depending upon which sent you to the detail view.

It would be nice to have a button on the HeroDetail view that can do that.

Add a go back button to the bottom of the component template and bind it to the component’s goBack() method.


goBack(): void {
window.history.back();
}

view raw

goBack.ts

hosted with ❤ by GitHub


<button onClick={() => this.goBack()}>go back</button>

view raw

goBack.html

hosted with ❤ by GitHub

Refresh the browser and start clicking. Users can navigate around the app, from the dashboard to hero details and back, from heroes list to the mini detail to the hero details and back to the heroes again.

Conclusion

This concludes part 4 of this series and next up is dealing with HTTP requests in StencilJS, coming soon!

You can review the full example of this tutorial on this Github tag: https://github.com/nerdic-coder/stencil-tour-of-heroes/tree/v4.0

Now continue on to the final part of this Stencil series: Stencil — Tutorial: Tour of Heroes Part 5

Advertisement

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 )

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: