Directives are functions that can customize how lit-html renders values. Template authors can use directives in their templates like other functions:
However, instead of returning a value to render, the directive controls what gets rendered to its location in the DOM.
Internally, lit-html uses the
Part interface to represent the dynamic DOM associated with a binding. A directive has access to the
Part associated with its binding. For example, it can find the current value of the part and set a new value for the part.
To create a directive, pass a factory function to lit-html's
The factory function can take optional arguments for configuration and values to be passed in by the template author.
The returned function is called each time the part is rendered. The
part argument is a
Part object with an API for directly managing the dynamic DOM associated with expressions. Each type of binding has its own specific Part object:
NodePartfor content bindings.
AttributePartfor standard attribute bindings.
BooleanAttributePartfor boolean attribute bindings.
EventPartfor event bindings.
PropertyPartfor property bindings.
Each of these part types implement a common API:
value. Holds the current value of the part.
setValue. Sets the pending value of the part.
commit. Writes the pending value to the DOM. In most cases this happens automatically—this method is only required for advanced use cases, like asynchronous directives. See Asynchronous directives for more information.
Here's an example of a directive that takes a function, and evaluates it in a try/catch block to implement exception-safe expressions:
safe directive can be used to wrap a function:
This example increments a counter on every render:
The user uses it in a template by passing in an initial value:
Limiting a directive to one binding typePermalink to “Limiting a directive to one binding type”
Some directives are only useful in one context, such as an attribute binding or a content binding. If placed in the wrong context, the directive should throw an appropriate error.
This example shows a directive that should only work in a content binding (that is, a
Asynchronous directivesPermalink to “Asynchronous directives”
Directives are invoked during the render process. The previous example directives are synchronous: they call
setValue on their parts before returning, so their results are written to the DOM during the render call.
Sometimes, you want a directive to be able to update the DOM asynchronously—for example, if it depends on an asynchronous event like a network request.
When a directive sets a value asynchronously, it needs to call the part's
commit method to write the updated value to the DOM.
Here's a trivial example of an asynchronous directive:
Here's an equally trivial example of the directive in use:
Here, the rendered template shows "Waiting for promise to resolve," followed one second later by "Promise is resolved."
Maintaining state between rendersPermalink to “Maintaining state between renders”
If your directive needs to maintain state between renders, you can rely on the fact that the
Part object representing a given location in the DOM stays the same between calls to
render. In the
renderCounter example, the part's value serves as the state.
If you need to store more complicated state, you can can use a
WeakMap, using the
Part as a key.
Why a WeakMap? Using a weak map ensures that the
Part objects and state data can be garbage collected when they're no longer in use, preventing a memory leak. For more information, see the MDN page on WeakMap.
Repeating directives in content bindingsPermalink to “Repeating directives in content bindings”
Sometimes you want a directive to manage multiple nested parts. For example, a directive that renders a list of items (like
repeat) might create a nested part for each item. Keeping separate parts lets you manipulate them efficiently: for example, you can change the value of a single part without re-rendering the entire list.
To create nested parts, you construct
NodePart instances and associate them with specific locations in the DOM. The section of DOM controlled by a given
NodePart needs to be delimited by static nodes that serve as markers. (lit-html usually uses comment nodes for these markers.)
As shown in the diagram, the nodes managed by the
NodePart appear between its
endNode. The following code creates and adds a new, nested part inside an existing part (the "container part").
The end result looks something like this:
appendIntoPart method creates the marker nodes and inserts the nested part for you. In some cases, you may need to manually manage the marker nodes (for example, if you're inserting a nested part into the middle of the child list). In this case, you can use code like this:
Putting it all together—the following example directive takes a value and inserts it into the DOM twice by creating two nested parts. As shown in Maintaining state between renders, it uses a
WeakMap to store these nested parts.
NodePart class provides a number of other convenience methods, including other methods for adding nested parts, and a
clear method to remove all of the DOM associated with a part. See the
NodePart API docs for details.