Vaadin Component Type Definitions for TypeScript
- Importing Type Declarations
- The
@query
Decorator - Event Listeners
- Custom Events
- Renderer Functions
- Generic Types
- Registering Elements
Vaadin components come with TypeScript definitions located in d.ts
files published to npm
together with the source code.
Using proper types for the web components helps to make the client-side logic more reliable. Depending on the IDE you use, TypeScript definitions can also enable better code completion and auto-import.
When using Visual Studio Code, it’s recommended that you install the lit-plugin
, which provides syntax highlighting and type-checking support for Lit templates. You can also use the lit-analyzer
CLI tool by the same author for type checking.
Importing Type Declarations
Every TypeScript definition file exports type declarations, which describe the public API provided by the web component. To use the type declarations in your client-side views, you need to import them using the type-only import syntax:
import type { Dialog } from '@vaadin/dialog';
The type declarations are only used by the TypeScript compiler to analyze statically the code and collect information about types. They’re removed during the Vite
compilation step and don’t end up in the resulting JavaScript bundle.
The following sections give examples of how to improve the developer experience when using Vaadin components by applying correct types for elements, their properties, and events.
The @query
Decorator
Lit discourages manual DOM operations in favor of binding data to the component template declaratively using reactive properties. However, it can sometimes be convenient to get a reference to the component instance and use it for method calls.
The suggested way of storing a reference in Lit-based views is the @query
decorator. This expects the type of the corresponding web component to be specified:
@query('#dialog')
private dialog!: Dialog;
Note the !
sign used in the property declaration. This is the definite assignment assertion operator. It tells the TypeScript compiler that the dialog
property is initialized indirectly and doesn’t need a default value.
Event Listeners
When using event listeners in Lit-based views, it might be useful to get a reference to the event target and then access its value. One common case for this is handling a change
event.
The Lit syntax for binding listeners doesn’t let TypeScript know the type of component to which the listener belongs. You need to handle this manually by specifying the correct type:
render() {
return html`
<vaadin-text-field @change="${this.onChange}"></vaadin-text-field>
`;
}
onChange(event: Event) {
const field = event.composedPath()[0] as TextField;
// <vaadin-text-field> has a value property.
console.log(field.value);
}
The as
keyword is a type assertion, often called a "type cast". It’s a hint for the TypeScript compiler, forcing it to use the type provided explicitly. In this particular case, it’s safe, as the component that dispatches the event is known in advance.
Custom Events
Vaadin components offer type definitions for custom events, making it easier to use them in TypeScript. In particular, this allows you to get a proper type for the detail
property, which has the type of any
by default. To use the event types, you need to import them manually:
import type { TextFieldInvalidChangedEvent } from '@vaadin/text-field';
render() {
return html`
<vaadin-text-field
label="Username"
@invalid-changed="${this.onInvalidChanged}"
></vaadin-text-field>
`;
}
onInvalidChanged(event: TextFieldInvalidChangedEvent) {
// `detail` is an object with boolean `value` property.
console.log(event.detail.value);
}
Renderer Functions
A renderer function is a class method used to render parts of the DOM of a component. Renderer functions accept one or more arguments. To access renderer functions without TypeScript warnings, you need to get them typed by importing and using the corresponding type declarations:
protected indexRenderer(
root: HTMLElement,
column: GridColumn,
model: GridItemModel<Person>
) {
render(html`<div>${model.index}</div>`, root);
}
The GridItemModel
type declaration is exported by the @vaadin/grid
npm package. It’s a TypeScript interface describing the properties available on the model
, including index
. The GridColumn
declaration is the type of the <vaadin-grid-column>
component.
See also the Grid content example, which demonstrates how to define renderer
functions on the Grid
columns to provide rich content based on the column.
Generic Types
Certain Vaadin components — namely Grid, Combo Box, CRUD, and Virtual List — support setting the items
property as an array of objects. Typically, when you use a component, you know the expected type of the object in advance and can provide it, explicitly.
In TypeScript, this could be achieved by using generic types. For example, a Grid type can be specified when using a @query
decorator:
@query('#grid')
private grid!: Grid<Person>;
This type isn’t inferred by the component, internally. Therefore, the same generic type needs to be passed to the model
argument of the renderer
function:
nameRenderer(
root: HTMLElement,
column: GridColumn,
model: GridItemModel<Person>
) {
// `model` is an object with an `item` property of type `Person`
const person = model.item;
render(html`<div>${person.firstName} ${person.lastName}</div>`, root);
}
A type argument can be also used in event listeners to detect changes of some properties:
onSelectedItemChanged(event: ComboBoxSelectedItemChangedEvent<Person>) {
// `detail` is an object of a `value` property of type `Person`
console.log(event.detail.value);
}
Generic type arguments can be passed to various properties and TypeScript interfaces listed below.
Combo Box Generic Types
The following Combo Box properties support generic types:
-
dataProvider
-
filteredItems
-
items
-
renderer
-
selectedItem
The following Combo Box interfaces support generic type arguments:
-
ComboBoxDataProvider
-
ComboBoxDataProviderCallback
-
ComboBoxItemModel
-
ComboBoxRenderer
-
ComboBoxSelectedItemChangedEvent
CRUD Generic Types
The following CRUD properties support generic types:
-
dataProvider
-
editedItem
-
items
The following CRUD interfaces support generic type arguments:
-
CrudCancelEvent
-
CrudDataProviderCallback
-
CrudDataProvider
-
CrudDeleteEvent
-
CrudEditEvent
-
CrudEditedItemChangedEvent
-
CrudItemsChangedEvent
-
CrudSaveEvent
Grid Generic Types
The following Grid properties support generic types:
-
activeItem
-
cellClassNameGenerator
-
dataProvider
-
dragFilter
-
dropFilter
-
expandedItems
-
items
-
rowDetailsRenderer
-
selectedItems
The following Grid column properties support generic types:
-
footerRenderer
-
headerRenderer
-
renderer
The following Grid interfaces support generic type arguments:
-
GridActiveItemChangedEvent
-
GridBodyRenderer
-
GridCellActivateEvent
-
GridCellClassNameGenerator
-
GridCellFocusEvent
-
GridColumnReorderEvent
-
GridColumnResizeEvent
-
GridDataProvider
-
GridDragAndDropFilter
-
GridDragStartEvent
-
GridDropEvent
-
GridExpandedItemsChangedEvent
-
GridEventContext
-
GridItemModel
-
GridRowDetailsRenderer
-
GridSelectedItemsChangedEvent
Registering Elements
When creating custom elements to use with client-side views, you might want to instruct TypeScript to use your definitions. This isn’t required, but sometimes it improves the developer experience and allows you to write less code.
For example, if you use the querySelector()
and querySelectorAll()
methods in your custom element, which return an Element
instance of an array of them, the easiest workaround would probably be to use a type cast:
const items = this.renderRoot.querySelectorAll('color-item') as ColorItem[];
items.forEach(item => {
// access item properties
});
However, this approach isn’t clean, as it requires you to write as ColorItem[]
every time the method is called. A better alternative would be to register a class corresponding to the HTML tag name in the built-in HTMLElementTagNameMap
interface:
declare global {
interface HTMLElementTagNameMap {
'color-item': ColorItem;
}
}
Now, every time you call querySelector()
or querySelectorAll()
with a corresponding tag name, the TypeScript compiler can automatically infer the proper type, making the type cast no longer necessary:
const items = this.renderRoot.querySelectorAll('color-item');
items.forEach(item => {
// access item properties
});
The TypeScript definitions for Vaadin components provide these registrations. This allows you to avoid writing type casts when using certain DOM methods. Apart from the query methods, this applies to other methods, such as createElement()
and closest()
.