Tutorial hero
Lesson icon

Creating Reusable Cross-Framework Components with StencilJS

Originally published April 16, 2019 Time 10 mins

StencilJS is the technology behind the most recent version of the Ionic Framework, which allows Ionic’s components to be used with any framework or with no framework at all (opposed to the situation before where it was tied specifically to Angular). The reason StencilJS can be used to achieve this is that it provides a simplified way to build web components which are just native to the web, rather than to a specific framework.

You don’t need to use StencilJS in order to use Ionic, but we can use StencilJS if we want to create our own custom components that can be used anywhere. That means you could create a web component or web components that you could use in your framework of choice, but those same components could also be used with any other framework.

In the same way that Ionic provides a set of components to use in our applications, we could also provide our own set of components. This could be extremely useful in many situations, but especially where you are creating components that you intend to reuse across multiple projects. Imagine the time and effort you could save as a studio that develops mobile applications for clients and you slowly build up a collection of your own generic components that you can repurpose and reuse anywhere you like.

This also decouples a lot of the work you are doing from whatever framework you happen to be using at the time. In future, if you decide to change frameworks or adopt additional frameworks, you would be able to just directly drop in a lot of the work you have already done.

In this tutorial, we are going to walk through how you can create a collection of your own web components using StencilJS. We will then walk through how to use those components inside of an Ionic/Angular application, but you could use the same components elsewhere.

1. Create a new StencilJS Project

We can create multiple different types of StencilJS projects. Although StencilJS is a tool for building web components (not a framework), it can also be used as a kind of “pseudo-framework” for building applications in place of something like Angular/React/Vue.

A lot of the other StencilJS tutorials I have published on this site focus on that aspect, but in this case, we are going to be creating a StencilJS project specifically for the purpose of creating generic web components to be used elsewhere. This is easy to set up, as it is one of the default starting options when generating a new project.

Run the following command to generate a new StencilJS project:

npm init stencil

When prompted with the following options, choose component:

? Pick a starter › - Use arrow-keys. Return to submit.

   ionic-pwa     Everything you need to build fast, production ready PWAs
   app           Minimal starter for building a Stencil app or website
❯  component     Collection of web components that can be used anywhere

Fill out the additional prompts with information about your project, and then your project will be created. If you like, you can run the project right away to see it in the browser by making it your working directory and running:

npm start

You will find that the start project already includes an example component that you will see when you run the project:

Hello, World! I'm Stencil 'Don't call me a framework' JS

We will keep that component there to demonstrate that we can create a collection of multiple components to be used elsewhere, but we are also going to create our own additional component. This won’t be anything fancy, this is mostly for the sake of demonstrating the use of web components outside of StencilJS rather than learning the aspects of StencilJS itself.

2. Create the Web Components

To keep things simple, but still at least a little interesting, we are going to create a Marauder’s Map component. Harry Potter aficionados will already be familiar with the purpose of the such a map, but I’ll quickly summarise for those not familiar with the wizarding world. The Marauder’s Map displays the locations of people around Hogwarts Castle, but it is written in a special kind of magic ink such that not anybody can read it. Attempts to read the map without knowing the proper incantation will result in personal insults.

That is what our component will do, it will accept a reader prop and display insults to that person, e.g:

<my-marauders-map reader="Josh"></my-marauders-map>

should result in:

Mr Moony presents his compliments to Josh, and begs them to keep their abnormally large nose out of other people's business.

Mr Prongs agrees with Mr Moony, and would like to add that Josh is an ugly git.

Mr Padfoot would like to register his astonishment that an idiot like that ever became a Professor.

Mr Wormtail bids Josh good day, and advises them to wash their hair, the slimeball.

Create new files at src/components/my-marauders-map/my-marauders-map.tsx and src/components/my-marauders-map/my-marauders-map.css

Add the following to src/components/my-marauders-map/my-marauders-map.tsx:

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

@Component({
  tag: 'my-marauders-map',
  styleUrl: 'my-marauders-map.css',
  shadow: true,
})
export class MyMaraudersMap {
  /**
   * The first name
   */
  @Prop()
  reader: string;

  render() {
    return (
      <div>
        <p>
          Mr Moony presents his compliments to {this.reader}, and begs them to
          keep their abnormally large nose out of other people's business.
        </p>

        <p>
          Mr Prongs agrees with Mr Moony, and would like to add that
          {this.reader} is an ugly git.
        </p>

        <p>
          Mr Padfoot would like to register his astonishment that an idiot like
          that ever became a Professor.
        </p>

        <p>
          Mr Wormtail bids {this.reader} good day, and advises them to wash
          their hair, the slimeball.
        </p>
      </div>
    );
  }
}

The insults aren’t particularly creative here as they are just taken directly from Harry Potter but inserting the readers’ name in place of Snape’s, but you could perhaps come up with some more random/carefully crafted insults if you wanted.

To test out that this component behaves as you want it to, you can add it to the src/index.html file:

<!DOCTYPE html>
<html dir="ltr" lang="en">
  <head>
    <meta charset="utf-8" />
    <meta
      name="viewport"
      content="width=device-width, initial-scale=1.0, minimum-scale=1.0, maximum-scale=5.0"
    />
    <title>Stencil Component Starter</title>
    <script src="/build/mycomponent.js"></script>
  </head>
  <body>
    <my-component
      first="Stencil"
      last="'Don't call me a framework' JS"
    ></my-component>

    <my-marauders-map reader="Josh"></my-marauders-map>
  </body>
</html>

and then run the project with npm start. If all is well, you should see the following:

Hello, World! I'm Stencil 'Don't call me a framework' JS
Mr Moony presents his compliments to Josh, and begs them to keep their abnormally large nose out of other people's business.

Mr Prongs agrees with Mr Moony, and would like to add that Josh is an ugly git.

Mr Padfoot would like to register his astonishment that an idiot like that ever became a Professor.

Mr Wormtail bids Josh good day, and advises them to wash their hair, the slimeball.

3. Publish the Web Components

Before we can use these components in another project (an Ionic/Angular application in this particular case), we will need to publish them on npm - either publicly or privately.

You should first modify your package.json file to give your package an appropriate name and description (and any other details you want to fill out).

You can then build your web components using the following command:

npm run build

Then all that is left to do is publish the package to npm. If you already have your npm username set up you will just need to run:

npm publish

and the package will be published to npm. If you do not already have an NPM account set up, you will need to run the following command first:

npm adduser

Here is the package I published for this example (and you can use this package for the next part if you like): joshmorony-example-components

4. Use the Web Components

Now that our set of web components have been built and published, we can make use of them where we like. As I mentioned, we are going to cover the specific case of Ionic/Angular but you can find other framework integrations here: Framework Integration.

In order to use our web components in an Angular application, we must first install the package we published by running the following command in the Angular project:

npm install joshmorony-example-components --save

We must then modify main.ts to import and make a call to defineCustomElements:

import { enableProdMode } from '@angular/core';
import { platformBrowserDynamic } from '@angular/platform-browser-dynamic';

import { AppModule } from './app/app.module';
import { environment } from './environments/environment';

import { defineCustomElements } from 'joshmorony-example-components/dist/loader';

if (environment.production) {
  enableProdMode();
}

platformBrowserDynamic()
  .bootstrapModule(AppModule)
  .catch((err) => console.log(err));
defineCustomElements(window);

Any module that you want to use your web components in you must add the CUSTOM_ELEMENTS_SCHEMA to:

import { NgModule, CUSTOM_ELEMENTS_SCHEMA } from '@angular/core';
import { CommonModule } from '@angular/common';
import { IonicModule } from '@ionic/angular';
import { FormsModule } from '@angular/forms';
import { RouterModule } from '@angular/router';

import { HomePage } from './home.page';

@NgModule({
  imports: [
    CommonModule,
    FormsModule,
    IonicModule,
    RouterModule.forChild([
      {
        path: '',
        component: HomePage,
      },
    ]),
  ],
  schemas: [CUSTOM_ELEMENTS_SCHEMA],
  declarations: [HomePage],
})
export class HomePageModule {}

Finally, we can just drop the web component in wherever we want to use it now:

<ion-header>
  <ion-toolbar>
    <ion-title> Ionic Blank </ion-title>
  </ion-toolbar>
</ion-header>

<ion-content>
  <div class="ion-padding">
    <my-marauders-map reader="Josh"></my-marauders-map>
  </div>
</ion-content>

Summary

There is a little more work involved in doing this initially rather than just creating a custom component directly in the Angular application (or whatever framework you are using). However, there are a lot of benefits if you do take the time to learn StencilJS and to create generic web components that can be used anywhere.

Learn to build modern Angular apps with my course