DEV Community

Cover image for ๐Ÿ•Ž 8 Days of Web Components Tips
Benny Powers ๐Ÿ‡ฎ๐Ÿ‡ฑ๐Ÿ‡จ๐Ÿ‡ฆ
Benny Powers ๐Ÿ‡ฎ๐Ÿ‡ฑ๐Ÿ‡จ๐Ÿ‡ฆ

Posted on • Updated on • Originally published at bennypowers.dev

๐Ÿ•Ž 8 Days of Web Components Tips

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 {}
Enter fullscreen mode Exit fullscreen mode

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();
  }
}
Enter fullscreen mode Exit fullscreen mode

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:

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 ? '๐Ÿ•ฏ' : ' ';
  }
}
Enter fullscreen mode Exit fullscreen mode
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>
    `);
  }
}
Enter fullscreen mode Exit fullscreen mode

live demo

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>
    `;
  }
}
Enter fullscreen mode Exit fullscreen mode

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>
Enter fullscreen mode Exit fullscreen mode

Top comments (7)

Collapse
 
yamita profile image
Yamita • Edited

Excuse me Benny. I'm a baby dev considering if I should learn web components using Lit. Do you mind clarifying these elementary points please? So I know that I'm on the correct learning path and don't lose 6months on something that may not work.

Can web components (in an easy way without thousands of dependencies)...

1) Be used in frameworks like NextJS or does it only work on client side rendered things like react/angular/vue? OR do we not even need a front end library and just use the web components UI (this confuses me, is it web components + Next or web components + nothing)?

2) Can they be dynamic and use long lists of data from our backend in our custom UI elements? e.g. our DB stores [stock_price] and [datetime] that update every minute. Can we now create a custom UI element that displays the 'current' [stock_price] which updates every minute from data in our DB rather than interactive user input data. OR a more complex example is UI widget that displays the stock price in the last week from our DB as a line chart, Will this UI element be able to fetch the 'value' data from our [stock price] and [datetime] 'key' data from our backend frameworks DB or will it just appear blank and we have to somehow hook them together? I got the impression in your intro blogs that the dev or user defines the data displayed in a customer element - in this would it would mean each UI element is rendering as a 'static elements', is this assumption wrong?.

3) Your 'lets build web components 8parts' series did not cover StencilJS, why? I wanted to hear your thoughts of Stencil vs Lit - because I considered defaulting to Ionic UIs (which I think can be tweaked with stencil) and creating my own with stencil/lit when Ionic doesn't fit my UI needs.

IF Q 1 and 2 = True, I will learn a web component lib instead of react. Then go straight to learning NextJS and adding my UI to NextJS apps.

Bonus if points if you can answer this.
4) Can I use StylusCSS when making my Lit custom UIs? If so how (I want to write them using html + typescript + stylus syntax)!?

Collapse
 
bennypowers profile image
Benny Powers ๐Ÿ‡ฎ๐Ÿ‡ฑ๐Ÿ‡จ๐Ÿ‡ฆ

Hi Yamita!

  1. Web components work anywhere HTML works, so yes. That being said, some frameworks play games with HTML and the DOM in ways that make it harder to use web technologies on their own terms. I don't have much experience using web components in NextJS myself, but a quick search turned up this first result:

  2. Sure, web components have a variety of means of accepting complex data of any variety. You can use a "light DOM as data" approach, assign DOM properties, or use stringified attributes, depending on what works for you. For your stock ticker example, you could write a reactive controller which updates the host with stock prices based on API calls. See the ClockController example and adapt to use asynchronous fetch() calls.

  3. in my personal opinion, I prefer Lit over Stencil because Lit is oriented more as a set of additional features on top of platform features, whereas Stencil is oriented more as a framework. That being said, you are perfectly able to use web components written in stencil inside your Lit components and vice-versa

  4. Yes, you can use CSS preprocessors with Lit or other web components libraries, but it will add complexity. my rollup-plugin-lit-css and esbuild-plugin-lit-css help smooth that process out. In my personal opinion, modern CSS is great and doesn't need any help from pre-processors

Thanks for the questions :)

Collapse
 
yamita profile image
Yamita

Thank you Benny you're the best!

After further research the developer of CodePen wrote an article: blog.bitsrc.io/web-component-why-y...

There he says "When compared with Web Components, React has the following advantages:
-Allows you to change the underlying data model with state
-Trigger UI changes based on the state
-Writing components using functions and hooks"

But isn't he simply wrong? I know for a fact you can change the STATE of UI using stencil (probably with Lit as well). Not sure about functions or hooks though because I don't know what they are yet (no reactJS experience).

Let me see if I understand your point 2.

Are you saying we CAN use our DB data and have it dynamically update in our web component, but NOT DIRECTLY? It has to be done via an API call? So the web component cannot access the DB directly. Instead we have to write an API with our backend framework that allows a web component to 'fetch' the data array from the API. And from that point do a calculation in the DOM on what it fetched? Is this how we would build different calculator types using web component and data from our DB?

A very simple example would be, DB storing house prices in a suburb. How would we get the web component to get a list of the e.g. 150 records in the first place? So we can calculate the average price... so SUM of 150 records / 150 = X average price.

PS: Rather than fetching 150 records. We just make the API run a calculation summing the records before it passes it onto the web component, then the web component only needs 1 request, not 150 requests. And on that 1 request it can do Sum of Records / 150 = average price. I assume this would be a more effective way to program that reduces server load and saves money?

If you want to visualize that describes my needs better. I actually wrote an article here. No one has been brave enough to reply yet but you're welcome to take a crack at it.
dev.to/yamita/can-web-components-u...

Thread Thread
 
bennypowers profile image
Benny Powers ๐Ÿ‡ฎ๐Ÿ‡ฑ๐Ÿ‡จ๐Ÿ‡ฆ

But isn't he simply wrong?

yup.

(probably with Lit as well)

changing state with lit: lit.dev/docs/components/properties/

functions or hooks

BTW it's my personal opinion that you don't really need those abstractions, but it's nice to know they're there

It has to be done via an API call?

Yeah if you want to run calculations on or access data from a webserver you need the browser to make a network request. JavaScript has a built-in http client called fetch()

I assume this would be a more effective way to program that reduces server load and saves money?

Seems legit to me

Thread Thread
 
yamita profile image
Yamita

Thanks a million! You have given be the inspiration to continue the web component journey and help others like myself when I become good enough! <3

I appreciate that you treated me nicely and saw that I have okay problem solving skills but need to pickup on the syntax now. These questions were more to understand possibilities and get a clearer picture of the end target and what can be achieved. I think conversations like these are still useful to market the web component technology. ^.^

I'll read about the functions/hooks web components libs tomorrow and learn about react hooks just to understand it conceptually before I decide if if it's important. But right now Lit sounds like an amazing option with a nice community and will probably be where I start my front end journey. Goodnight!

Collapse
 
binyamin profile image
Binyamin Green

This is just what I needed. Also, happy Chanukkah!

Collapse
 
bennypowers profile image
Benny Powers ๐Ÿ‡ฎ๐Ÿ‡ฑ๐Ÿ‡จ๐Ÿ‡ฆ

ื’ื ืœืš ืื—ื™ ื—ื•ืจืฃ ื—ื