Photo by Gavin Allanwood on Unsplash

Stefan helps developers to become Framework Agnostic. If you find his content helpful, you can buy him a coffee here and get his exclusive e-book “10 reasons to go framework-agnostic” for free!

Allow me to show you how to build modals with Stencil.

Coding with relatively new tools can be challenging due to the lack of (good) tutorials. Especially when you have a specific thing like a modal overlay in mind.

So, that’s why I’ve decided to build my own modal overlay component with StencilJS and share my experiences and to write this tutorial to help you understand the possibilities of StencilJS.

Check out this repo for the source.

What is Stencil?

Stencil is a compiler that generates Web Components which combines the best concepts of the most popular frameworks into a simple build-time tool. It provides extra APIs that makes writing fast components simpler. APIs like Virtual DOM, JSX, and async rendering make fast, powerful components easy to create, while still maintaining 100% compatibility with Web Components.

The developer experience is also tuned, and comes with live reload and a small dev server baked in to the compiler.

Stencil was created by the Ionic Framework team to help build faster, more capable components that worked across all major frameworks.

Let’s start building a Modal

One common and frequently used UI component is a modal window, a content container that displays above the rest of the content and contains a clear call to action. It’s sometimes accompanied by an overlay that covers the rest of the web page or app. And that is what we’re going to build today!

Component design

The name of our component is: my-component . This is the default name generated by the starter and for convenience and keeping this tutorial in scope, I’ve decided to let the name as is. You’re completely free to rename it at any time.

The component has the following attributes:

Open<boolean>: Shows the modal window component; Transparent<boolean>: Toggles transparency of the overlay;

The components has the following method:

render: Renders content to the screen.

Setting up our application

Before we can start building our component we have to setup a development environment, which is very easy with the starter, provided by our friends from Stencil.

Note: You need npm v6 or higher in order to complete this tutorial!

Stencil can be used to create standalone components, or entire apps. Open a new terminal window and run the following command:

npm init stencil

After running init you will be provided with a prompt so that you can choose the type of project to start.

Since we’re building a single component, select the third option, which generates a development environment, installs all necessary dependencies and scaffolds the component’s code.

The next step is to provide a name for the project. For this tutorial it doesn’t really matter which name you pick. I wanted to be extremely original and named my project: my-modal .

Stencil provides the developer with a very basic, hello world example to understand a bit better what’s going on and how an application is organized. It’s not in the tutorial’s scope to elaborate on this, but you can read more about this here.

Alright! We’re done setting up our application’s infrastructure!

Writing our first tests

Since we’re creating a component TDD style, let’s start of right away with writing our first tests.

Stencil provides many utility functions to help test Jest and Puppeteer. For example, a component’s Shadow Dom can be queried and tested with the Stencil utility functions built on top of Puppeteer. Tests can not only be provided mock HTML content, but they can also go to URLs of your app which Puppeteer is able to open up and test on Stencil’s dev server.

The starter generated a test file already ( ./src/components/my-component/my-component.e2e.ts ) , which contains a few basic unit tests to get the gist on testing Web Components. Open this file, study it and replace it with the following content:

What happened?

We import the necessary packages from the testing libraries provided in Stencil core. We create a my-component element and append it to the DOM. This is done in the beforeEach method, which is called before every unit test. We expect that my-component successfully renders in the DOM. We expect that we find a div decorated with a class, called overlay .

Let’s run our tests with the following command:

npm run test

…Only to see that all of them fail. So let’s change that immediately!

Open ./src/components/my-component/my-component.tsx , study the example code and replace it with the following:

Notice the following parts of the component:

The @Component decorator. This decorator provides metadata about our component to the compiler. You find a default ES6 JavaScript Class right under the decorator. This is where you’ll write the bulk of your code to bring the component to life. In the class you find the render() function. This is used by the component to render content to the screen. It returns JSX. In the html template, you find a <slot/> container, which is a placeholder inside a web component that you can fill with your own markup.

Learn more about Stencil components here.

If we run the tests again, they all pass. Hooray! Now it’s time to implement more logic and make our component actually useful.

Opening the modal window

Before we start implementing the logic for opening the modal, let’s write some more tests.

We want to cover the following cases:

It should display the overlay while the modal is open. If set, the overlay should be transparent.

This results in the following test cases, which you need to add to the test file:

Whoah! What happened here?

We set different properties ( open & transparent ) with the component.setProperty() method. We wait for the changes made to the component with the waitForChanges() method. Both Stencil and Puppeteer have an asynchronous architecture, which is a good thing for performance. Since all calls are async, it's required that await page.waitForChanges() is called when changes are made to components. We check if the element is decorated with the expected CSS classes.

Read more about testing Stencil components here.

And, of course, if we run our tests they’ll fail miserably again, so let’s open the component’s code ( my-component.tsx ) and make the tests pass.

What did we do?

We’ve added properties open & transparent . They can be recognized by the @Prop() decorator, a class that’s imported from @stencil/core . We’ve changed our class definition in the HTML template and check if we need to make the modal visible and make the overlay transparent.

Closing the modal window

In order to close the modal, we need to set the open property to false . We’ll implement a method for that in our sample code later.

Let’s write the tests necessary and make them pass:

All tests are in green again and we have a fully operating modal, which looks terrible…

Add the following styling classes to ./src/components/my-component/my-component.css :

Looks much better now!

The proof is in the pudding

All what we’ve done is writing tests and make them pass by adding code to the component, but the real proof is to check if it actually works, so let’s update our index.html file.

Here we create an instance of the component itself and decorate it with an id in order to be able to access it later on. We also added a button, which act as trigger to open the modal.

In the script we created two references. One for the modal component and one for the button. Next we’ve created two events to test if opening and closing it works properly.

Last, but not least, we added an eventListener to the modal itself, which listens for a click event. If it’s triggered, the modal will close.

It’s a wrap

That’s it! There’s much room to improve this component, like extending the modal content container template with a header and footer, cancel/confirmation buttons, etc. etc. If you see any points for improvement or spot a mistake in my code, please please leave create a pull request or leave a message in the comments!

Feel free to check out the code in this git repository.

About the author