Mixins
Class mixins are a pattern for sharing code between classes using standard JavaScript. As opposed to "has-a" composition patterns like reactive controllers, where a class can own a controller to add behavior, mixins implement "is-a" composition, where the mixin causes the class itself to be an instance of the behavior being shared.
You can use mixins to customize a Lit component by adding API or overriding its lifecycle callbacks.
Mixin basics
Permalink to “Mixin basics”Mixins can be thought of as "subclass factories" that override the class they are applied to and return a subclass, extended with the behavior in the mixin. Because mixins are implemented using standard JavaScript class expressions, they can use all of the idioms available to subclassing, such as adding new fields/methods, overriding existing superclass methods, and using super
.
For ease of reading, the samples on this page elide some of the TypeScript types for mixin functions. See Mixins in TypeScript for details on proper typing of mixins in TypeScript.
To define a mixin, write a function that takes a superClass
, and returns a new class that extends it, adding fields and methods as needed:
To apply a mixin, simply pass a class to generate a subclass with the mixin applied. Most commonly, users will apply the mixin directly to a base class when defining a new class:
Mixins can also be used to create concrete subclasses that users can then extend like a normal class, where the mixin is an implementation detail:
Because class mixins are a standard JavaScript pattern and not Lit-specific, there is a good deal of information in the community on leveraging mixins for code reuse. For more reading on mixins, here are a few good references:
- Class mixins on MDN
- Real Mixins with JavaScript Classes by Justin Fagnani
- Mixins in the TypeScript handbook.
- Dedupe mixin library by open-wc, including a discussion of when mixin usage may lead to duplication, and how to use a deduping library to avoid it.
- Mixin conventions followed by Elix web component library. While not Lit-specific, contains thoughtful suggestions around applying conventions when defining mixins for web components.
Creating mixins for LitElement
Permalink to “Creating mixins for LitElement”Mixins applied to LitElement can implement or override any of the standard custom element lifecycle callbacks like the constructor()
or connectedCallback()
, as well as any of the reactive update lifecycle callbacks like render()
or updated()
.
For example, the following mixin would log when the element is created, connected, and updated:
Note that a mixin should always make a super call to the standard custom element lifecycle methods implemented by LitElement
. When overriding a reactive update lifecycle callback, it is good practice to call the super method if it already exists on the superclass (as shown above with the optional-chaining call to super.updated?.()
).
Also note that mixins can choose to do work either before or after the base implementation of the standard lifecycle callbacks via its choice of when to make the super call.
Mixins can also add reactive properties, styles, and API to the subclassed element.
The mixin in the example below adds a highlight
reactive property to the element and a renderHighlight()
method that the user can call to wrap some content. The wrapped content is styled yellow when the highlight
property/attribute is set.
Note in the example above, the user of the mixin is expected to call the renderHighlight()
method from their render()
method, as well as take care to add the static styles
defined by the mixin to the subclass styles. The nature of this contract between mixin and user is up to the mixin definition and should be documented by the mixin author.
Mixins in TypeScript
Permalink to “Mixins in TypeScript”When writing LitElement
mixins in TypeScript, there are a few details to be aware of.
Typing the superclass
Permalink to “Typing the superclass”You should constrain the superClass
argument to the type of class you expect users to extend, if any. This can be accomplished using a generic Constructor
helper type as shown below:
The above example ensures that the class being passed to the mixin extends from LitElement
, so that your mixin can rely on callbacks and other API provided by Lit.
Typing the subclass
Permalink to “Typing the subclass”Although TypeScript has basic support for inferring the return type for the subclass generated using the mixin pattern, it has a severe limitation in that the inferred class must not contain members with private
or protected
access modifiers.
Because LitElement
itself does have private and protected members, by default TypeScript will error with "Property '...' of exported class expression may not be private or protected." when returning a class that extends LitElement
.
There are two workarounds that both involve casting the return type from the mixin function to avoid the error above.
When a mixin does not add new public/protected API
Permalink to “When a mixin does not add new public/protected API”If your mixin only overrides LitElement
methods or properties and does not add any new API of its own, you can simply cast the generated class to the super class type T
that was passed in:
When a mixin adds new public/protected API
Permalink to “When a mixin adds new public/protected API”If your mixin does add new protected or public API that you need users to be able to use on their class, you need to define the interface for the mixin separately from the implementation, and cast the return type as the intersection of your mixin interface and the super class type:
Applying decorators in mixins
Permalink to “Applying decorators in mixins”Due to limitations of TypeScript's type system, decorators (such as @property()
) must be applied to a class declaration statement and not a class expression.
In practice this means mixins in TypeScript need to declare a class and then return it, rather than return a class expression directly from the arrow function.
Supported:
Not supported: