> Markdown version of [Write a ViewModel](https://vaadin.com/docs/latest/building-apps/views/mvvm/write-viewmodel). Section index: [llms.txt](https://vaadin.com/docs/latest/building-apps/llms.txt)

# Write a ViewModel

A ViewModel holds the state of one view in signals, derives the view’s rules from that state, and offers the actions a user can take as methods. This guide builds the ViewModel of the ticket triage view that the [overview](https://vaadin.com/docs/latest/building-apps/views/mvvm.md#the-example) describes.

> **Tip: Part of a Series**
>
> This is part 1 of the **Structure Views with MVVM** series. Read the [overview](https://vaadin.com/docs/latest/building-apps/views/mvvm.md#when-to-use-mvvm) first to decide whether your view needs a ViewModel.

## <a id="create-a-plain-java-class"></a>Create a Plain Java Class

The ViewModel is a plain Java class. It isn’t a Spring bean, and it has no `@UIScope` or `@RouteScope` annotation. The view creates it with `new`, and passes in the application services it needs. The ViewModel then lives exactly as long as the view instance does. Put the ViewModel in the package of its view, and keep it package-private:

TicketsViewModel.java

`TicketsViewModel.java`

```java
class TicketsViewModel {

    private final TicketService ticketService; // The Model

    TicketsViewModel(TicketService ticketService) {
        this.ticketService = ticketService;
    }
}
```

The ViewModel uses nothing from the `com.vaadin.flow.component`, `com.vaadin.flow.router`, or `com.vaadin.flow.data.binder` packages, and it never calls `UI.getCurrent()`. It uses only the signals in `com.vaadin.flow.signals`, and the Model. This is what lets you [test it](https://vaadin.com/docs/latest/building-apps/views/mvvm/test-viewmodel.md) with plain JUnit.

The constructor only stores the services. It doesn’t load any data, because Vaadin can create a view without showing it. The view tells the ViewModel when to load, as [Bind the View](https://vaadin.com/docs/latest/building-apps/views/mvvm/bind-view.md#create-the-viewmodel-in-the-view) shows.

## <a id="keep-the-state-in-signals"></a>Keep the State in Signals

All the state of the view lives in private signals. Every other field is `final`: services and constants.

`TicketsViewModel.java`

```java
private final ValueSignal<String> searchText = new ValueSignal<>("");
private final ValueSignal<Status> statusFilter = new ValueSignal<>(null);
private final ValueSignal<List<Ticket>> tickets = new ValueSignal<>(
        List.of());
// The row the user selected in the list
private final ValueSignal<Ticket> selected = new ValueSignal<>(null);
// The ticket in the editor, as last saved
private final ValueSignal<Ticket> opened = new ValueSignal<>(null);
// The ticket in the editor, with the user's edits
private final ValueSignal<Ticket> draft = new ValueSignal<>(null);
```

The ticket appears in three signals because the user sees three different things: the row selected in the list, the ticket in the editor as it was last saved, and that ticket with the user’s changes. Most of the time, all three hold the same ticket.

Keep the values in the signals immutable, for example as records with `withX()` methods:

Ticket.java

`Ticket.java`

```java
public record Ticket(long id, String title, Status status, String assignee) {

    public Ticket withTitle(String title) {
        return new Ticket(id, title, status, assignee);
    }

    public Ticket withStatus(Status status) {
        return new Ticket(id, title, status, assignee);
    }

    public Ticket withAssignee(String assignee) {
        return new Ticket(id, title, status, assignee);
    }
}
```

Change a value by replacing it, with `set()` or `update()`: `draft.update(ticket → ticket.withTitle(title))`. Keep lists immutable too, for example by creating them with `List.copyOf()` or `Stream.toList()`. Never change an object that you read from a signal in place: the signal can’t see that change, so nothing updates. If a value has to be a mutable bean, change it with `modify()`; see [Working with Mutable Values](https://vaadin.com/docs/latest/flow/ui-state/local-signals.md#working-with-mutable-values).

## <a id="derive-dont-sync"></a>Derive, Don’t Sync

When a value can be computed from other state, make it a computed signal with `Signal.computed()` or `map()`:

`TicketsViewModel.java`

```java
private final Signal<Boolean> editing = draft.map(Objects::nonNull);
private final Signal<Boolean> dirty = Signal
        .computed(() -> !Objects.equals(draft.get(), opened.get()));
private final Signal<Boolean> confirmingDiscard = Signal
        .computed(() -> !Objects.equals(selected.get(), opened.get()));
private final Signal<String> titleError = draft.map(
        d -> d != null && d.title().isBlank() ? "Title is required" : null);
private final Signal<Boolean> canSave = Signal
        .computed(() -> dirty.get() && titleError.get() == null);
```

Never keep a second signal in step with another by hand, in a command or in an effect. A derived signal can’t fall out of step, and it states the rule in one place. In the example, `confirmingDiscard` isn’t stored at all: the view is waiting for the user to keep or discard their changes exactly when the selected row differs from the opened ticket.

A computed signal runs its callback every time its value is read, so keep the callback cheap and free of side effects. It may call a pure domain function, such as a pricing rule, but never a service that reads a database or calls a remote system. Load data in a command instead. If a computation is expensive and several UI components read it, wrap it in `Signal.cached()`; see [Caching with Signal.cached()](https://vaadin.com/docs/latest/flow/ui-state/effects-computed.md#caching-with-signal-cached).

## <a id="expose-read-only-signals"></a>Expose Read-Only Signals

The view reads the state through methods that return read-only signals: `asReadonly()`, `map()`, or a computed signal. Never return a `ValueSignal`:

`TicketsViewModel.java`

```java
Signal<String> searchText() {
    return searchText.asReadonly();
}

Signal<List<Ticket>> tickets() {
    return tickets.asReadonly();
}

Signal<String> title() {
    return draft.map(d -> d == null ? "" : d.title());
}

Signal<Boolean> canSave() {
    return canSave;
}
```

Because the view can’t set the signals, every change goes through a command. The ViewModel’s methods then list exactly what the user can do, and a test can check every rule through them.

## <a id="offer-commands"></a>Offer Commands

Commands are the methods that the view calls when the user does something. Name them after what the user means to do, such as `select(ticket)`, `search(text)`, `save()`, or `keepEditing()`. A change to a field is a command too: `setTitle()` is what the title field calls when the user types.

`TicketsViewModel.java`

```java
void refresh() {
    tickets.set(
            ticketService.search(searchText.peek(), statusFilter.peek()));
}

void search(String text) {
    searchText.set(text == null ? "" : text);
    refresh();
}

void filterByStatus(Status status) {
    statusFilter.set(status);
    refresh();
}

void setTitle(String title) {
    draft.update(d -> d.withTitle(title));
}
```

React to a change in the command that makes it, not in an effect. When a new search text means reloading the tickets, the `search()` command does the reload. A ViewModel has no `Signal.effect()`, because an effect needs a UI component as its owner. It has no `Signal.unboundEffect()` either, because such an effect stays active until you remove it.

Read signals with `peek()` in commands, and with `get()` in computed signals. Calling `get()` outside a computed signal or an effect throws an `IllegalStateException`.

## <a id="return-outcomes-keep-conditions-as-state"></a>Return Outcomes, Keep Conditions as State

When the view has to react once to a command, for example by showing a notification or by navigating to another view, the command returns the outcome. The outcome can be a `boolean`, an enum, or a sealed interface of result records:

`TicketsViewModel.java`

```java
// Returns whether the ticket was saved
boolean save() {
    if (!canSave.peek()) {
        return false;
    }
    Ticket saved = ticketService.save(draft.peek());
    open(saved);
    refresh();
    return true;
}
```

Anything that stays on the screen is state instead: an error that the user has to fix, a marker that a save is in progress, or a question waiting for an answer. For example, a failed save that only needs a notification is an outcome. A conflict that the form keeps showing until the user resolves it is state, and belongs in a signal.

## <a id="keep-what-a-component-shows-as-state"></a>Keep What a Component Shows as State

When the ViewModel rejects a value in a field’s write callback, the field shows the signal’s value again. A grid’s selection doesn’t: the grid keeps showing the row the user clicked, while the signal keeps the old selection ([flow-components#10575](https://github.com/vaadin/flow-components/issues/10575)). Therefore, when the ViewModel can’t accept a new selection right away, it stores the selection as state, and settles it with a later command:

`TicketsViewModel.java`

```java
void select(Ticket ticket) {
    selected.set(ticket);
    if (!dirty.peek()) {
        open(ticket);
    }
}

// Throws away the changes, and opens the selected ticket
void discardChanges() {
    open(selected.peek());
}

// Goes back to the opened ticket, and keeps its changes
void keepEditing() {
    selected.set(opened.peek());
}

private void open(Ticket ticket) {
    selected.set(ticket);
    opened.set(ticket);
    draft.set(ticket);
}
```

When the user clicks another ticket while the opened ticket has unsaved changes, `select()` stores the new selection but doesn’t open it. The selected row then differs from the opened ticket, so `confirmingDiscard` becomes `true`, and the view asks the user what to do. `discardChanges()` opens the selected ticket. `keepEditing()` sets the selection back to the opened ticket, and the grid’s selection follows it.

The same applies to every UI component that shows state: if the user can see it, it’s in a signal.

## <a id="split-a-growing-viewmodel"></a>Split a Growing ViewModel

When a ViewModel holds groups of state that have little to do with each other, split it along the parts of the view. The ViewModel of the route view creates child ViewModels, such as a `FiltersViewModel` and an `EditorViewModel`, and exposes them. Each child UI component binds to its own ViewModel. Rules that span several parts stay in the parent.

When one part follows another, the parent passes the change on in its command:

```java
void select(Long ticketId) {
    selected.set(ticketId);
    editor.open(ticketId); // The child ViewModel
}
```

Don’t let the child ViewModel follow a signal of its parent in an effect. That needs an effect owner, and it hides the flow that a command states in one place.

Next, [bind the view](https://vaadin.com/docs/latest/building-apps/views/mvvm/bind-view.md) to the ViewModel.
