Skip to content

template vs render()

This mental model attempts to reduce the cognitive complexity of authoring components:

  1. The template is a read-only property (initialized with a get keyword) that represents how the component view is rendered.
  2. There is a render() method that triggers a view render.
  3. This render() method is automatically called under the hood every time an attribute value changed.
  4. You can optionally call this render() method at any point to trigger a render if you need (eg, if you have private unobserved properties that need to manually trigger a render)
  5. Overriding the render() function for handling a custom template is also possible. Here’s an example of using lit-html: View on CodePen ↗

See it live: Templating demo ↗ for the two template kinds, and Render reconciliation demo ↗ for what an in-place re-render preserves: focus, caret position and an uncommitted input value all survive.

A component’s template can contain other components, nested as deep as you like:

class CounterBoard extends WebComponent {
static props = { title: 'Board' }
get template() {
return html`
<h3>${this.props.title}</h3>
<counter-row name="alpha"></counter-row>
<counter-row name="bravo"></counter-row>
`
}
}

Each nested component owns the DOM it renders for itself. When an outer component re-renders, the reconciler patches the props it passes down to a nested element (that is how data flows from parent to child) but never touches the element’s own children, so a nested component keeps its rendered content and any internal state even when an ancestor re-renders for an unrelated reason. Data flows down as attributes, so pass values a nested component can read back from an attribute: primitives, or objects/arrays that survive a JSON round-trip. See it live: Nested composition demo ↗.

The one exception is slot projection: children you write inside a shadow-DOM component’s tag are your content, projected into its <slot>, so the parent keeps reconciling those. A light-DOM component, by contrast, renders over its own children, so pass data to it through attributes rather than as projected children.