Stencil vs other tools: Creating a simple app

There are many tools and frameworks for front end developers like React, Angular, Vue. New frameworks keep popping up all around the web, they can offer an interesting mix of features from other frameworks, as well as introducing new ways to look at web development.

Today I want to talk about Stencil. It is a tool that allows creating web components, as well as creating your own apps. Say for example you write an app in Stencil and you have other apps, written in other frameworks (Actually Stencil doesn’t position itself as framework, but you can write apps with it just as easily), you can use the components from a Stencil app in those frameworks right away, because Stencil components are web components.

Stencil uses typescript for creating components, like Angular, but unlike Angular it does not have separate templates, instead its components have a render method like React and in that method you can write a modified version of JSX and the files themselves have a tsx extension (JSX in typescript).

In this post I am going to create a food recipes application with Stencil. It will feature a main page, with categories sidebar and popular recipes list, a category page and a recipe page. I will as usual compare the process of creating an application with other modern tools for web development, particularly popular web frameworks like React, Angular, Vue.

First things first; we need to know how to install the tool and start a simple application. Stencil does not have any cli program, but it does not have a lot of configuration either, so it is not like the old days of React, when you had to configure webpack for all the options you may have needed and all the supporting tools you needed to install. In fact you don’t have to install anything, you should only have git, node and npm, the tools that required by other frameworks anyway. Bootstrapping an app is simply a matter of cloning a git repository for the Stencil starter app

git clone my-app

Then we start the live reload server with:

npm start

Once we do that we have the following project structure:


Our application is going to live in src directory, which has the following structure:

├── assets
│   └── icon
│       ├── favicon.ico
│       └── icon.png
├── components
│   ├── app-home
│   │   ├── app-home.scss
│   │   ├── app-home.spec.ts
│   │   └── app-home.tsx
│   ├── app-profile
│   │   ├── app-profile.scss
│   │   ├── app-profile.spec.ts
│   │   └── app-profile.tsx
│   └── my-app
│       ├── my-app.scss
│       ├── my-app.spec.ts
│       └── my-app.tsx
├── components.d.ts
├── index.html
└── manifest.json

In the src directory we are only interested in index.html, which you can use to add your styles and scripts and the components directory, where all your components are going to live. We are going to create two mock data files in the src directory: mock_recipes.ts and mock_categories.ts.

Stencil starter project already includes a home page and a profile (detail) page. So if you are an app that shows some list on a front page and needs a detail page for the elements of that list you are already set and do not need to bother creating those components and setting up routes.

Stencil has a router inspired by react-router and with that ultimately by Ember router. Routes are defined in JSX-like way of react-router., with route components specifying mapping between a url and a component that is going to render a view for that url. The routes are already set in the main component of the starter app. We only need to change them slightly to match the structure and component names of our application. Modify the my-app component to look like this:

import { Component } from '@stencil/core';

  tag: 'my-app',
  styleUrl: 'my-app.scss'
export class MyApp {
  render() {
    return (
            <stencil-route url='/' component='app-home' exact={true}>
            <stencil-route url='/recipe/:id' component='app-recipe'>

As you can see the component is defined like in Angular, using a @Compnent decorator, you specify how the component should be referenced by other components by using tag property (it’s called selector in Angular) of the config object passed to the decorator. The import path is also quite similar to Angular.

But as I said earlier, the component has a render method that returns the structure of the components markup. In this particular case we have a stencil-router component that contains stencil-route components.

Currently we have two routes: a landing page route, that is handled by app-home component, the exact attribute matches exactly “/“ url and not urls that start with “/“ and contain other characters. The other route is a detail page for recipes.

As you can see we do not import components like in React or Vue, they are just available for us, like in an Angular template. But in Angular for a component to be available to use in another component’s template, you need to declare it in a module that the using component belongs to, or import to that module a module that exports the component that should be used in a template. So it is extremely easy and convenient to use components Stensil way.

Next, let’s take a look at what app-home component looks like:

import { Component, State } from '@stencil/core';
import Categories from '../../mock_categories';

  tag: 'app-home',
  styleUrl: 'app-home.scss'
export class AppHome {

  @State() categories: { id: number, name: string }[];
  componentWillLoad() {
    this.categories = Categories;

  render() {
    return (
      <div class="app-home-container">
        <app-categories-sidebar categories={this.categories} />
        <app-popular />

Here we have more decorators and TypeScript. We import mock category data from a file to pass to a categories sidebar component, that is going to be used throughout our application. Inside our component’s class declaration we use a State decorator for categories property. Stencil, like React explicitly defines state, but it does that using a decorator, instead of defining it in a constructor, and since this is typescript we can attach a type to a property for a typechecker to prevent wrong types creeping into our application.

If you are coming from React, you may be familiar with performance enhancing techniques used by React, such as shouldComponentUpdate and PureComponent. Stencil components act like PureComponent in React, that means, you don’t have to worry about performance of rerendering with unchanged state and props values.

As with PureComponent, you have to take into account the possibility of deep changes (object/array mutations) to props or state not being detected, also Stencil doesn’t have a forceUpdate method, so even if you want to rerender manually in case of deep changes, you will not be able to do so. Therefore the recommended way of changing arrays and object props and state is through assigning a new object or an array. For an array it looks like this (ES6 way):

this.arrayProp = [ …this.arrayProp, anotherValue ];

Also you can use methods that produce new arrays like slice, filter, map.

For an object it looks like this (ES6 way):

this.objectProp = { …this.objectProp };

We have a lifecycle comopentWillLoad method that is written in a similar to React fashion. Stencil has less lifecycle hooks than React, it seems like React has more flexibility regarding a component’s lifecycle, on the other hand you may prefer a simple approach provided by Stencil.

Also Stencil doesn’t have compoentWillReceiveProps, instead it has a PropWillChange decorator, and you can call your method whatever you want (good for debugging). It allows you to track changes to a particular prop, but there seems to be no mechanism for you to know when any change to any prop has occurred.

For our categories we use mock data, in a real app you would want to use data that is comming from a server, at least for the first time. We assign the categories data to categories state property of the compoent, to trigger the render method (In our demo app we do not need to use state, but in a real app you would want to implement the functionality in a way close the above code).

In the render method we are going to use a app-categories-sidebar component and app-popular components. We are going to pass the categories data as a state property of the app-home component. Unlike React you don’t have to reference the property like, you just use

So far we’ve seen that when writing components in Stencil you should not use imports and and exports for them. It is a very dangerous pattern though, because it makes you forget what is the meaning of those keywords.

Let’s now dive into app-categories sidebar. It looks like this:

import { Component, Prop } from '@stencil/core';

  tag: 'app-categories-sidebar',
  styleUrl: 'app-categories-sidebar.scss'
export class AppCategoriesSidebar {

  @Prop() categories: { id: number, name: string }[]

  render() {
    return (
        { => <li>{}</li>)}

Here we have a Prop decorator instead of a State decorator, which also gets imported from @stencil/core. We do not have a state in this component yet, as it is pretty simple and only transforms incoming data into a list of categories.

Continuing my analogy with React, Stencil has a notion of props. And again like state, Stencil uses a property decorator to denote a prop. Here it gets similar to how Angular requires a component to specify a Input and a Output decorators for a property that should be accessible from the outside of a component. But Stencil does not differentiate between Input and Output props, it just treats them as React. As with state you get the reference to a prop via, rather than

Here in app-categories-sidebar component we render a list of categories from an array using array’s map function.

That’s it with categories sidebar component for now. Let’s take a look at popular items component (app-popular). This component is going to show popular items based on app’s users’ votes (for our short tutorial app we just use Math.random shamelessly).

It looks pretty similar to other components. It imports mock recipes data:

import Items from '../../mock_recipes';

It has a items state property and a componentWillLoad property:

@State() items: {
  id: number,
  name: string,
  photo: string,
  description: string,
  categories: number[]

componentWillLoad() {
  this.items = Items;

Our list consists of recipes objects, that have and id, a name, a photo, and a list of categories, this  recipe belongs to.

It is going to contain a list with popular items like this:

<ul class="popular-items">
  { =>
    <li class="popular-item-container">
      <div class="card">
        <div class="popular-item-image">
          <div class="card-image">
            <img src={`/images/${}`} />
            <span class="card-title">{}</span>
        <div class="card-content">
        <div class="card-action">
          <a href="#">View</a>

Again we use array’s map function to render the list. One thing to note here is that we use Materialize css library. card, card-image, card-title, card-content, card-action css classes are coming from that library. As Stencil is a relatively new tool, it doesn’t have its own material design library, so we are using Materialize here. We are using a CDN version of the library by adding the following to src/index.html file:

<link rel="stylesheet" href="">

With some styling applied, our home page is going to look like this:

Let’s now work on a category page. For it to be available we need to add a category route to our main app file my-app.tsx:

<stencil-route url='/category/:id' component='app-category'>

Now we need to add a app-category folder to src and app-category.tsx and a style file to app-category folder. app-category.tsx is slimilar to app-popular.tsx, you need to import category and item data there, like this:

import Categories from '../../mock_categories';
import Items from '../../mock_recipes';

The component is going to have app-category tag, and a class name will be AppCategory. It will have the following state and prop properties:

@State() category: { id: number, name: string }
@State() items: { id: number, name: string, photo: string, description: string }[]
@Prop() match: any

It will get the necessary data the following way:

componentWillLoad() {
  this.category = Categories.find(category => ===;
  this.items = Items.filter(item =>
    item.categories.find(category => category ===

Where match prop is coming from stencil router and contains parameters that are needed by that component. Here we just pick a category using a url parameter, and pick items that current category contains.

The markup is going to look the same as app-popular component and use Materialize card.

We now only have to make a detail page for a recipe. It is pretty straightforward to do as long as you have data for that recipe.

The code is going to look like this:

import { Component, State, Prop } from '@stencil/core';
import Items from '../../mock_recipes';

  tag: 'app-recipe',
  styleUrl: 'app-recipe.scss'

export class AppRecipe {

  @State() recipe: { id: number, name: string, photo: string, description: string }
  @Prop() match: any

  componentWillLoad() {
    this.recipe = Items.find(item => ===;

  render() {
    return (
        <img src={`/images/${}`} alt={} />

Right now Categories sidebar only appears on the main page. But what if we want it to appear on every page so user can easily navigate to another category’s page? Let’s fix that.

We need to move the following code from app-home component to my-app:

import Categories from '../../mock_categories';


@State() categories: { id: number, name: string }[];

componentWillLoad() {
  this.categories = Categories;

In my-app we need to add additional prop to every route, that needs to access the categories list:

componentProps={{ categories: this.categories }}

It may not look DRY. The second method of having categories data available to all component is the use of a state management solution like redux to save categories in a store and connect every component to the store.

In every component that is going to have app-categories-sidebar we need to add the following:

@Prop() categories: { id: number, name: string }[];

And use the categories-sidebar component:

<app-categories-sidebar categories={this.categories} />

Right now our app has several routes that display data, but they are not interactive. A user cannot click on an item and navigate to another page. We need to fix that.

First let’s make it possible to navigate to a category page by clicking the sidebar:

We need to wrap every category in a list in app-categories-sidebar in a stencil-route-link component like this:

<stencil-route-link url={`/category/${}`}>

This will navigate to a category page of an item clicked, but it will not work if we are on a category page, because component is already loaded so componentWillLoad will not get called. To make this work, we need to use a method decorated with PropWillChange, which I talked about earlier. Since first loading a category page for the first time and changing an already loaded category page will require an action of changing state variables: category and items, we need to create a method for that action and call it from componentWillLoad and PropWillChange decorator:

updateState(categoryId) {
  this.category = Categories.find(category => === +categoryId);
  this.items = Items.filter(item =>
    item.categories.find(category => category === +categoryId)

Now we just need to call it from componentWillLoad the following way:


And PropWillChange will look like this:

categoryChanger(newVal: any) {

Also It would be nice to go to a detail page page by clicking on an item on the categories page and on the home page. We need to wrap the parts of an item that we want to be links into stencil route link:

<stencil-route-link url={`/recipe/${}`}>
  // Route link content goes here; like an image or an action button

Let’s also add a link to the home page at the top of the screen. Add this code to my-app render:

<h1><stencil-route-link url="/">Home</stencil-route-link></h1>

Now we are able to conveniently navigate between different parts of the application.

If you make a component that is going to be needed in another framework you can use it there as a web component, for example we could enhance our items in a category page list with additional details on hover or on some button click, and be able to use it in another framework that would need this functionality.

This concludes my comparison/tutorial post on Stencil. It is very easy to create a simple application with it. If you want to have an experience of developing with an API that you are already somewhat familiar with, but at the same time remove some limitations that the frameworks that you already know put on you, or you just want some fresh tool to try, Stencil may be the thing you are going to like.

You can find the complete source code for this tutorial on Github: