Intro to Web Components

Creating Custom Elements

Building Web Components

Build Tools

Stencil.js

LitElement

Installing Stencil

npm install @stencil/core

Stencil Init

npm init stencil

Stencil Decorators

@Prop()

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

@Component({
tag: 'say-hello'
})
export class SayHello {
@Prop() name: string;

render() {
return <p>Hello, World! I'm {this.name}.</p>;
}
}
<say-hello name="Carla"></say-hello>
<say-hello name="Carla">
<p>Hello, World! I'm Carla.</p>
</say-hello>

@State()

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

@Component({
tag: 'click-counter'
})
export class ClickCounter {
//...
  //...
@State() count = 0;

private incrementCount() {
this.count++;
}
//...
  //...
render() {
return (
<div>
<p>{this.count}</p>
<button
type="button"
onClick={() => this.incrementCount()}
>Plus 1</button>
</div>
);
}
}

@Watch()

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

@Component({
tag: 'say-hello'
})
export class SayHello {
@Prop() name: string;

@Watch('name')
updateName(newValue, oldValue) {
// Fire name-related event, perhaps...
}
//...
  //...
render() {
return <p>Hello, World! I'm {this.name}.</p>;
}
}

@Event()
@Listen()

import { Component, Event, EventEmitter, h } from '@stencil/core';

@Component({
tag: 'modal-trigger'
})
export class ModalTrigger {
// ..
  //...
@Event() openModal!: EventEmitter;

private triggerClick() {
this.openModal.emit();
}
//...
  //...
render() {
return (
<button onClick={() => this.triggerClick()}>
<slot />
</button>
);
}
}
import { Component, h, Listen, State } from '@stencil/core';

@Component({
tag: 'modal-window'
})
export class ModalWindow {
// ..
  //...
@State() isOpen = false;

@Listen('openModal')
openModalWindow() {
this.isOpen = true;
}
//...
  //...
render() {
const modalClasses = {
modal: true,
'modal--open': this.isOpen
};

return (
<section class={modalClasses}>
<slot />
</section>
);
}
}
<modal-trigger>
Log In
</modal-trigger>

<modal-window>
<form></form>
</modal-window>

Component Lifecycle Hooks

connectedCallback()

Every time it’s connected to DOM, before componentWillLoad()

disconnectedCallback()

Every time it’s disconnected from DOM

componentWillLoad()

Runs 1x after component 1st connects to DOM

componentDidLoad()

Called once after fully loaded & 1st render()

componentWillRender()

Before every render()

componentDidRender()

After every render()

componentWillUpdate()

When @Prop() or @State() change (not 1st render())

componentDidUpdate()

After update render() (but not 1st render())

Styling Web Components

Shadow DOM

Completely scoped styles:

Outer styles can’t leak in;
inner styles can’t leak out.

Shadow DOM Selectors

  • :host, :host()
  • :host-context()
  • ::slotted()
  • ::part() *
:host {
display: block;
}

:host(.is-blue) {
color: blue;
}

:host([aria-hidden="true"]) {
opacity: 0;
}
:host-context(article) {
width: 50%;
}

:host-context(aside) {
width: 100%;
}
::slotted(svg) {
  margin-right: 8px;
  width: 22px;
}

::slotted(svg *) {
  fill: var(--button-fg, #FFF);
}
render() {
return (<div>
<button part="trigger">Click Me</button>
</div>);
}
/* Outer CSS allowed through the Shadow DOM */
custom-element::part(trigger) {
color: dodgerblue;
}

* Implemented in Firefox/Chrome, not in Spec

Custom Props

CSS Custom Properties are the only things allowed to affect Shadow DOM elements.

export class CustomButton {
render() {
return (
<button class="button">
<slot />
</button>
);
}
}
/* Attached to Shadow DOM */
.button {
color: var(--button-fg, #FFF);
background: var(--button-bg, #1976D2);
}

.button:hover {
color: var(--button-fg-active, #FFF);
background: var(--button-bg-active, #1565C0);
}
/* Outer CSS: inherited by Shadow DOM */
.button-container--red {
--button-bg: #D32F2F;
--button-bg-active: #B71C1C;
}

Deploying Web Components

npm run build

Get /dist/* onto your server
or into your site build process.

Framework Integration

Instructions in Stencil Docs:

Angular | React | Vue | Ember

James Steinbach

Staff Front-End Engineer at Okta

jdsteinbach

Twitter | Github | Blog