TypeScript Definitions of Components

Vaadin components come with TypeScript definitions helping to use web components in TypeScript views. The type definitions are d.ts files generated every time when new release is published to npm.

In most of cases, TypeScript support does not require any changes to the code. At the same time, using proper types for the web components helps making the client side views 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 is recommended to install lit-plugin, which provides syntax highlighting and type checking support for Lit templates. You can also use 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 type-only import syntax:

import type { DialogElement } from '@vaadin/vaadin-dialog';

The type declarations are only used by the TypeScript compiler to statically analyze the code and collect information about types. They are removed during webpack compilation step and do not 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.

Query Decorator

While Lit discourages manual DOM operations in favor of binding data to the component template declaratively using reactive properties, sometimes it can 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. It expects a type of the corresponding web component to be specified:

@query('#dialog')
private dialog!: DialogElement;

Note the ! sign used in the property declaration. It is a definite assignment assertion operator. This tells the TypeScript compiler that the dialog property is initialized indirectly and does not need a default value.

Event Listeners

When using event listeners in Lit-based views, it might be useful to get the 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 does not let TypeScript know what type of a component the listener belongs to, so this needs to be handled manually by specifying a correct type:

render() {
  return html`
    <vaadin-text-field @change="${this.onChange}"></vaadin-text-field>
  `
}

onChange(event: Event) {
  const field = event.composedPath()[0] as TextFieldElement;
  // <vaadin-text-field> has a value property.
  console.log(field.value);
}

The as keyword is a type assertion, often also called a "type cast". It is a hint for the TypeScript compiler forcing it to use the type provided explicitly. In this particular case it is safe, as the component that dispatches the event is known in advance.

Custom Events

Vaadin components offer the set of type definitions for custom events making it easier to use them in TypeScript. In particular, this allows to get proper type for the detail property, which has type of any by default. To use the event types, you need to import them manually:

import type { TextFieldInvalidChangedEvent } from '@vaadin/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

Some Vaadin components like Grid and Dialog allow to pass the <template> element and then use it to stamp the content. This API is not compatible with Lit templates. Use renderer functions in Lit-based views instead.

Renderer function is a class method that accepts one or several arguments. To access them without TypeScript warnings, you need to get them typed by importing and using the corresponding type declarations:

protected indexRenderer(
  root: HTMLElement,
  column: GridColumnElement,
  model: GridItemModel<Person>
) {
  render(html`<div>${model.index}</div>`, root);
}

The GridItemModel type declaration is exported by the @vaadin/vaadin-grid npm package. It is a TypeScript interface describing the properties available on the model, including index. The GridColumnElement declaration is the type of <vaadin-grid-column> component.

See also the Grid content example that 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 using generic types. For example, a Grid type can be specified using @query decorator:

@query('#grid')
private grid!: GridElement<Person>;

Note that this type does not get inferred by the component internally. That’s why the same generic type needs to be passed to model argument of the renderer function:

nameRenderer(
  root: HTMLElement,
  column: GridColumnElement,
  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);
}

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 types 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 argument:

  • 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 argument:

  • 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 argument:

  • GridActiveItemChangedEvent

  • GridBodyRenderer

  • GridCellActivateEvent

  • GridCellClassNameGenerator

  • GridCellFocusEvent

  • GridColumnReorderEvent

  • GridColumnResizeEvent

  • GridDataProvider

  • GridDragAndDropFilter

  • GridDragStartEvent

  • GridDropEvent

  • GridExpandedItemsChangedEvent

  • GridEventContext

  • GridItemModel

  • GridRowDetailsRenderer

  • GridSelectedItemsChangedEvent

Virtual List Generic Types

The following Virtual List properties support generic types:

  • items

  • renderer

The following Virtual List interfaces support generic type argument:

  • VirtualListItemModel

  • VirtualListRenderer

Registering Elements

When creating your own custom elements for using with client side views, you might want to instruct TypeScript to use your definitions. This is not mandatory, but sometimes it improves developer experience and allows to write less code.

As an example, let’s look into using querySelector and querySelectorAll methods with your own custom elements. These methods return Element, so the easiest workaround would be probably to use a type cast:

const items = this.renderRoot.querySelectorAll('color-item') as ColorItem[];
items.forEach(item => {
  // access item properties
});

However, this approach is not clean, as it requires 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 when you call querySelector or querySelectorAll with a corresponding tag name, TypeScript compiler can infer the proper type automatically, 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 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.