๐ 8 Days of Web Components Tips
Benny Powers ๐ฎ๐ฑ๐จ๐ฆ
Posted on December 5, 2021
In honour of Hannukah this year, I undertook to write 8 web components tips, one for each night of the festival. Tonight is the 8th and final night of the festival. The mystics said that this night combines and contains aspects of each of the seven previous nights, so I'd like to share a compilation of those tips with the dev community.
Wishing you and yours a fully Lit Hannukah!
1st night: Adding Controllers via TypeScript Decorators ๐ฏ
Did you know you can add reactive controllers to an element via a class or field decorator? You don't even need to assign it to an instance property!
/**
* Adds a given class to a ReactiveElement when it upgrades
*/
export function classy(classString: string): ClassDecorator {
return function(klass) {
if (!isReactiveElementClass(klass))
throw new Error(`@classy may only decorate ReactiveElements.`);
klass.addInitializer(instance => {
// Define and add an ad-hoc controller!
// Look, mah! No instance property!
instance.addController({
hostConnected() {
instance.classList.add(classString);
},
});
});
};
}
@customElement('pirsumei-nissa') @classy('al-hanissim')
export class PirsumeiNissa extends LitElement {}
2nd night: Adding Controllers Inside Other Controllers ๐ฏ๐ฏ
Like a delicious sufganya (traditional holiday donut) with many fillings, a Lit component can have multiple reactive controllers, and controllers can even add other controllers
export class MutationController<E extends ReactiveElement> implements ReactiveController {
private logger: Logger;
mo = new MutationObserver(this.onMutation);
constructor(public host: E, public options?: Options<E>) {
// Add another controller
this.logger = new Logger(this.host);
host.addController(this);
}
onMutation(records: MutationRecord[]) {
this.logger.log('Mutation', records);
this.options?.onMutation?.(records)
}
hostConnected() {
this.mo.observe(this.host, this.options?.init ?? { attributes: true, childList: true });
}
hostDisconnected() {
this.mo.disconnect();
}
}
3rd night: Web Component Context API ๐ฏ๐ฏ๐ฏ
Did you know web components can have context? The protocol is based on composed events. Define providers & consumers, & share data across the DOM.
https://github.com/webcomponents-cg/community-protocols/blob/main/proposals/context.md
4th night: Using SASS, PostCSS, etc. ๐ฏ๐ฏ๐ฏ๐ฏ
Building #webcomponents with #SASS? (You probably don't need it but if you can't resistโฆ) you can develop using a buildless workflow with Web Dev Server and esbuild-plugin-lit-css
Want to use #PostCSS instead for sweet-sweet future CSS syntax? No problem
5th night: Stacking Slots ๐ฏ๐ฏ๐ฏ๐ฏ๐ฏ
Who doesn't like a piping hot stack of latkes?
Stack slots to toggle component states. Adding content into the outer slot automatically 'disables' the inner slot
State management in HTML! ๐คฏ
Check out @westbrook's blog on the topic:
Who doesn't love some `<slot />`s?
Westbrook Johnson ใป Nov 24 '21
6th night: Better TypeScript Imports ๐ฏ๐ฏ๐ฏ๐ฏ๐ฏ๐ฏ
In #TypeScript 4.5, if you set preserveValueImports
, you can import the class definitions of your element dependencies without worrying that TS will elide the side-effecting value.
import { LitElement } from 'lit';
import { customElement, property } from 'lit/decorators.js';
@customElement('lit-candle')
export class LitCandle extends LitElement {
@property({ type: Boolean }) lit = false;
render() {
return this.lit ? '๐ฏ' : ' ';
}
}
import { LitElement, html } from 'lit';
import { customElement, property, query } from 'lit/decorators.js';
import { LitCandle } from './lit-candle.js';
@customElement('lit-menorah')
export class LitMenorah extends LitElement {
@property({ type: Number }) night = 6;
// Although the value of `LitCandle` isn't used, only the type
// with `preserveValueImports`, TS 4.5 won't strip the import
// So you can be sure that `<lit-candle>` will upgrade
@query('lit-candle') candles: NodeListOf<LitCandle>;
render() {
return Array.from({ length: 8 }, (_, i) => html`
<lit-candle ?lit="${(i + 1) <= this.night}"></lit-candle>
`);
}
}
7th night: GraphQL Web Components ๐ฏ๐ฏ๐ฏ๐ฏ๐ฏ๐ฏ๐ฏ
Looking to add #GraphQL to your frontend? Give Apollo Elements a try. Use Apollo reactive controllers with lit+others, or try a 'functional' library like atomic
import { ApolloQueryController } from '@apollo-elements/core';
import { LitElement, html } from 'lit';
import { customElement } from 'lit/decorators.js';
import { HelloQuery } from './Hello.query.graphql';
@customElement('hello-query')
export class HelloQueryElement extends LitElement {
query = new ApolloQueryController(this, HelloQuery);
render() {
return html`
<article class=${classMap({ skeleton: this.query.loading })}>
<p id="error" ?hidden=${!this.query.error}>${this.query.error?.message}</p>
<p>
${this.query.data?.greeting ?? 'Hello'},
${this.query.data?.name ?? 'Friend'}
</p>
</article>
`;
}
}
8th night: Component Interop ๐ฏ๐ฏ๐ฏ๐ฏ๐ฏ๐ฏ๐ฏ๐ฏ
You don't need to use only #lit components in your #lit app
Mix old-school #Polymer 3 components with #vue js web components. Put #stencil js Microsoft's #FAST UI on the same page
It's your party!
<!DOCTYPE html>
<head>
<link rel="stylesheet" href="https://cdn.jsdelivr.net/npm/@shoelace-style/shoelace@2.0.0-beta.61/dist/themes/light.css">
<link rel="stylesheet" href="https://cdn.jsdelivr.net/npm/@ionic/core/css/ionic.bundle.css"/>
<script type="module" src="https://cdn.jsdelivr.net/npm/@shoelace-style/shoelace@2.0.0-beta.61/dist/shoelace.js"></script>
<script type="module" src="https://cdn.jsdelivr.net/npm/@ionic/core/dist/ionic/ionic.esm.js"></script>
<script type="module" src="https://unpkg.com/@microsoft/fast-components"></script>
<script type="module" src="https://unpkg.com/@patternfly/pfe-datetime@1.12.2/dist/pfe-datetime.js?module"></script>
<script type="module" src="https://unpkg.com/@material/mwc-button?module"></script>
</head>
<body>
<sl-card>
<pfe-datetime slot="header" type="relative" datetime="Mon Jan 2 15:04:05 EST 2010"></pfe-datetime>
<ion-img slot="image" src="https://placekitten.com/300/200"></ion-img>
<fast-progress-ring min="0" max="100" value="75"></fast-progress-ring>
<mwc-button slot="footer">More Info</mwc-button>
</sl-card>
</body>
Posted on December 5, 2021
Join Our Newsletter. No Spam, Only the good stuff.
Sign up to receive the latest update from our blog.