Docs

Documentation versions (currently viewingVaadin 25)
Documentation translations (currently viewingEnglish)

Bind the View

Learn how to create a ViewModel in a view, bind UI components to its signals, and call its commands from listeners.

With the state and the rules in the ViewModel, the view is left with the UI components, their layout, and the bindings between them and the ViewModel. This guide builds the view of the ticket triage example.

Tip
Part of a Series
This is part 2 of the Structure Views with MVVM series. It builds on the ViewModel from Write a ViewModel.

Create the ViewModel in the View

The @Route class creates the ViewModel in its constructor, and keeps it in a private final field. Spring injects the application services into the view’s constructor, and the view passes them on:

Source code
TicketsView.java

The view doesn’t load data in its constructor, because Vaadin can create a view without showing it. Instead, it calls the refresh() command after the user has navigated to the view. See Calling a Service on View Creation for more.

A field that you assign in its declaration can’t use the ViewModel, not even inside a lambda: the compiler reports that the final field might not have been initialized. Assign such fields, for example event listeners, in the constructor after the ViewModel.

Bind UI Components to Signals

The view binds each UI component to the ViewModel once, instead of pushing values into the UI components whenever something changes. Here is the list part of the view:

Source code
TicketsView.java

A field binds to a read-only signal and a command with bindValue(viewModel.x(), viewModel::setX). The field shows the signal’s value, and passes the user’s input to the command. The grid’s selection binds the same way, through asSingleSelect() or asMultiSelect(). For the other properties of a UI component, use the one-way bindings, such as bindText(), bindVisible(), bindEnabled(), and bindReadOnly(), and render a list of UI components with bindChildren(). Component Bindings lists them all.

Vaadin doesn’t have bind methods for a grid’s items or a field’s error message. For those, the view uses bindItems() and bindError() from a small helper class in the application, which the last section of this guide shows. For anything else that has no bind method, write an effect with Signal.effect(), as the view does to open a dialog.

The editor part of the view works the same way:

Source code
TicketsView.java

By default, a text field passes its value to the ViewModel only when the user presses Enter or moves the focus elsewhere. The title field uses the eager value change mode, so that the ViewModel sees every keystroke and enables Save as soon as the user has typed a change. The search field uses the lazy mode, which waits until the user stops typing. The Text Field page lists the value change modes.

Call Commands from Listeners

A listener calls one command, and at most acts on its result. It doesn’t call services, and doesn’t decide anything that the ViewModel could decide. The Save button calls save(), and shows a notification only when the command reports that it saved the ticket. The Discard button calls discardChanges() without checking anything first.

Sometimes a framework callback needs an answer at once, and can’t wait for a signal to change. For example, beforeLeave() has to decide whether to postpone the navigation. In such a callback, the view reads a signal of the ViewModel with peek(), such as viewModel.dirty().peek(). Protect Unsaved Changes shows the whole callback.

Leave UI Work to the View

The view does what only a UI can do: it shows notifications and dialogs, navigates to other views, moves the focus, and reads route parameters. The ViewModel decides that something happens, by returning an outcome or by changing its state. The view decides how it shows up on the screen.

Pass State to Child Components

A large view is built from child UI components, such as a list and an editor, as Compose with Components describes. A ViewModel doesn’t change how you split the view into components. It changes where the state lives: in the ViewModel, instead of in the view.

A one-off child component, which exists only inside this view, receives its state from the view. Pass it either the whole ViewModel, or only the signals and commands that it needs:

Source code
Java
class TicketGrid extends VerticalLayout {

    TicketGrid(Signal<List<Ticket>> tickets, Signal<Ticket> selected,
            SerializableConsumer<Ticket> select) {
        Grid<Ticket> grid = new Grid<>();
        grid.addColumn(Ticket::title).setHeader("Title");
        bindItems(grid, tickets).setIdentifierProvider(Ticket::id);
        grid.asSingleSelect().bindValue(selected, select);
        add(grid);
    }
}
Source code
Java
// In the view
add(new TicketGrid(viewModel.tickets(), viewModel.selected(),
        viewModel::select), new TicketEditor(viewModel));

The two children don’t know about each other, and the view doesn’t pass anything from one to the other. The grid calls select(), and the editor follows the state of the ViewModel. Without a ViewModel, the view would listen to the grid, and call a setter on the editor.

A reusable component, such as a card that shows a person in many views, knows nothing about the ViewModel. It accepts generic values, and reports the user’s actions with events or callbacks, which the parent passes on to commands:

Source code
Java
master.addEmployeeSelectedListener(
        event -> viewModel.select(event.getEmployee()));

To show state from a signal, a reusable component takes a read-only Signal rather than a ValueSignal, or offers a bind method next to its setter. The SignalPropertySupport helper makes such a method easy to write; see SignalPropertySupport Helper. The component then works both in plain views and in views with a ViewModel.

Only the route view creates a ViewModel. If the ViewModel has child ViewModels, the parent ViewModel creates them, as Split a Growing ViewModel describes.

Bind Grids & Error Messages

Vaadin doesn’t have bind methods for a grid’s items, or for a field’s error message (flow#26829). Copy the following class into your application, and import its methods statically in each view that has a ViewModel:

Source code
SignalBindings.java

The class has three helpers:

Helper Binds

bindItems(grid, items)

A signal holding a list to the items of a grid

bindItems(grid, query, fetch, count)

Items loaded page by page to a grid, refreshed whenever the query signal changes; see Load Data Page by Page

bindError(field, error)

A signal holding an error message to a field, where null means that the value is valid

Each helper is an effect owned by the UI component, so the binding ends when the UI component is detached. Bind each grid and field with one line:

Source code
Java
bindItems(grid, viewModel.tickets()).setIdentifierProvider(Ticket::id);
bindError(title, viewModel.titleError());

Keep the following in mind when you use the helpers:

Data View

Each bindItems() returns the grid’s data view, as setItems() does. Set an identifier provider on it when a save replaces an item with a new record, as in the example. The grid then recognizes the saved ticket as the selected row.

Selection

Bind the selection separately, with grid.asSingleSelect().bindValue(…​) or grid.asMultiSelect().bindValue(…​). The helpers set the items once, and refresh them on every change. Calling setItems() again, for example from an effect, would replace the data provider. That always clears the selection, and the selection binding passes the cleared selection to the ViewModel as null (flow-components#10575).

Select-All Checkbox

In multi-select mode, a grid updates the select-all checkbox in its header only when the selection changes, and treats an empty grid as having all its rows selected. When the rows change, the checkbox would be left in the wrong state (flow-components#10553). The in-memory bindItems() updates the checkbox from the rows and the selection. Call setSelectionMode(SelectionMode.MULTI) before bindItems(), so that the helper sees the mode.

Customizing

When a grid needs something that the helpers don’t do, such as a configurable filter or an estimated count, write the effect yourself, starting from the helper’s code.

Grids Only

The item helpers take a Grid on purpose, because other UI components react to changed items in their own ways. See Change the Options of a Select or Combo Box.