You signed in with another tab or window. Reload to refresh your session.You signed out in another tab or window. Reload to refresh your session.You switched accounts on another tab or window. Reload to refresh your session.Dismiss alert
Complete the Signals API and make it production-ready way of building reactive UI with Java and Vaadin by:
providing a high-level API in Components and core for common UI building cases and support specific and less often cases with the low-level API and helper classes,
updating Vaadin online documentation to use Signals in "Building Apps" articles at the first place and having components articles updated with the new methods, but still referencing the old way of managing components state.
updating start.vaadin.com, all the starter projects to use Signals.
Tier
Free
License
Apache 2.0
Motivation
Background
Vaadin 24.8, 24.9 have shipped the base Signals class hierarchy and helpers in ComponentEffect. Vaadin 25.0 has added Element-level binding methods and helpers in ElementEffect.
Vaadin 25.1 now aims to fully integrate Signal API into Vaadin Flow Components, complete the core API, make Signal classes serializable and conclude the overall design for the reactive UI state management, so that developers can use this way of building UI as a primary.
Problem
Overall problem: It is common in Vaadin to explicitly handle UI state change by users in the event listeners, maintain clean ups and set the UI state explicitly through the component's methods. This may lead to a boiler-plate code that makes the view classes be unreadable and hard-maintained. Signals on the other hand may simplify the UI development by implicitly handling the state change no matter what kind of source did this - single user event, multi-user events or asynchronous background event, and implicitly handling the component UI attached/detached state.
Another frequent pattern is to make components be the Java class fields in "view" Java classes, where they are accessed along the class in different places, e.g. from the listeners of another component, form binding codes or even from outer layout Java class. Signals can help separating the "state" from the components, leaving only static initialization (e.g. constant column settings) from the dynamically changed content. This may relief the need of component class fields, but making them state fields instead, whereas the component declaration can be local method-scoped.
The need of using UI.access() may bloat the code and requires a knowledge that this method exists and how to use it. Signals incapsulate this detail and basically use it under the hood, so the client code can just update the signal value and async updates work (of course the app needs to enable Push async updates).
Finally, whereas the Signal and related classes are available, it's not yet possible to code the reactive UIs using signals and without using existing low-level Element API, explicit effect functions and defining addValueChangeListener(), nor there is a way of binding signals to a form.
Solution
Signals integration to Vaadin Components would include:
Signal binding methods to Component class and mix-in interfaces for commonly used component properties (visible, enabled, value)
Component-specific signal binding methods (e.g. setHelperText)
Two-way signal binding for standalone field components and forms.
Ensure Signal API is serializable.
This in the end means the component provide bind methods for the component features for the following kind of bindings
one way binding (e.g. text)
two-way binding (e.g. value)
callbacks that use signal as a state (e.g. item label generator),
and the Binder API is adjusted/extended so it's features can use Signal and benefit from two-way bindings.
As an example of how the code may look like when using signals, guess the products price calculator add VAT to the base price and updates all product cards:
Example with the value change listeners:
NumberFieldvatField = newNumberField("VAT %");
// 1. Value change listener explicitly addedRegistrationvatChangeRegistration = vatField.addValueChangeListener(event -> {
productCards.forEach(productCard -> {
StringnewPriceLine = getUpdatedPrice(event.getValue());
// 2. Product component explicitly updated with the new priceproductCard.setPriceLine(newPriceLine);
});
});
// 3. Duplicated code that repeats the listener code for component initializationproductCards.forEach(productCard -> {
// initialize the UI nearly the same way
});
// 4. Manual cleanup on detachingvatChangeRegistration.remove();
Same example with signals:
// 3. State initialized once a signal is instantiatedReferenceSignal<Double> vatSignal = newReferenceSignal<>(24.0);
// 1. Two-way binding, no explicit listeners// 4. No registration, signal is deactivated on detachvatField.bindValue(vatSignal);
// Computed signalSignal<String> priceWithVatText = vatSignal.map(this::getUpdatedPrice);
// 2. Bind computed signal to the product productCards.forEach(productCard -> productCard.bindText(priceWithVatText));
Requirements
Common signal binding methods for components:
Component and Element classes shall allow visibility binding to a boolean signal by using void bindVisible(Signal<Boolean> visibleSignal) new methods. Component delegates to Element and has the same contract
HasEnabled mix-in and Element class shall allow enabling/disabling them with a boolean signal by using void bindEnabled(Signal<Boolean> enabledSignal) new methods. HasEnabled delegates to Element and has the same contract
Components implementing HasText mix-in interface shall allow binding their text to a signal by using void bindText(Signal<String> textSignal) new method. This should delegate to the same existing method in Element
Vaadin Flow components (including Flow basic HTML components) shall allow passing the string signal to the constructor to establish the one-way text binding to the signal value, where a text value is already supported by exiting constructors, e.g. new Span(textSignal)
Components implementing HasValue<T> mix-in interface and Element shall allow a signal binding to their value by using void bindValue(WritableSignal<T> valueSignal) new methods. Note that this is a two-way binding that also updates the signal value when a ValueChangeEvent is fired.
Component-specific signal bindings:
In addition to the above common cases in components, there are plenty of other more specific methods that components share thanks to mix-in interfaces or in the standalone flow component classes. For those cases the components shall allow the signal binding through the newly added methods:
One-way binding:
A regular one-way bindings for a commonly updated configurations, that is not covered by text, enabled and visibility bindings, some examples: HasTheme::bindThemeName(String, Signal<Boolean>), HasHelper::bindHelperText(Signal<String>), CheckboxGroup::bindRequired(Signal<Boolean>).
Two-way binding:
Two-way bindings are most common for setValue for components that implement HasValue (see next section for HasValue). The pattern is also applicable for some other cases that can be directly updated by the user such as whether a Details panel is opened, some examples: Details::setOpened(boolean), AppLayout::setDrawerOpened(boolean), Checkbox::setIndeterminate(boolean), MenuItemBase::setChecked(boolean), Crud::setDirty(boolean).
Other binding cases:
Other binding methods for component-specific state less common than visible, enable, text and value, e.g. UI::setLocale(Locale), new AvatarGroup(AvatarGroupItem[]), new VerticalLayout(Component[]) etc. See the sub-issue for the full list.
Form binding and HasValue:
Small adjustments should be done to the current Binder and HasValue APIs, that would make the most common cases possible for reactive form binding and input fields binding with signals. Designing a new signal-native form binding solution to replace Binder is out of scope of this PRD, if even decided.
Signal with the bean/record to edit:
It should be possible to bind a bean signal to the form with a help of ComponentEffect and this should be documented, no new API is added:
Cross-field validation:
Field-level validator callbacks should be run inside a signal effect so that changes to any signal used by the validator will be detected and trigger running the validator again. Practical cross-field validation furthermore requires that the value of other bindings are available as signals, by adding a signal-aware value getter to Binding:
varpasswordBinding = binder.bind(passwordField, "password");
binder.forField(confirmPasswordField)
.withValidator(value -> !useStrictValidationSignal.value() ||
Objects.equals(value, passwordBinding.value()),
"Must match the password")
.bind("confirmPassword");
Reacting to validation status changes: Binder should provide a readonly signal that contains the latest BinderValidationStatus state. The signal value should be updated whenever a BinderValidationStatusHandler would have been notified. This should work the same regardless of whether a status handler is explicitly configured or if the default that delegates to handleBinderValidationStatus is in use.
Updating other parts of the UI based on the bound bean:
We don't add any API to Binder to simplify non-field cases that might now be handled using ReadOnlyHasValue. Instead, we assume there's already a signal containing the currently edited bean and recommend binding directly to that signal. If such a signal doesn't exist, then the first step is to add one.
Two-way field bindings: Binder is overkill for simple cases such as a standalone search field. Instead, the natural approach would be to bind a signal directly to the value of an input field. This kind of binding would be most natural as a two-way binding so that the developer doesn't have to do a React-style value change listener that only updates the signal value.
The general assumption for iteration is that there's a container component that should have one component child of some specific type for each child signal in a ListSignal (or a computed signal with the same value type). To help implement this pattern, HasComponents interface shall provide a void bindComponentChildren method along with a similar bindElementChildren method in Element, that takes the bound list signal and the mapping function between signal value and rendered component instance. It's under question what signal type and what generic type is better to use for the method signature, it should be decided during implementation and API validation, but the general considerations are:
method shall have few overrides that allow using ReferenceSignal and ValueSignal depending on the need of concurrent modification
method shall allow using ListSignal as well as Signal<List<S extends WritableSignal>> or Signal<List<ValueSignal<T>> because ListSignal uses JSON representation for the value and that is not necessary in non-concurrent context.
These methods and bindText are mutually exclusive so that each one throws a BindingActiveException if any binding is present. While bound, methods for appending or removing children also throw, as does removeFromParent for any child that is attached through a binding
Helper method ComponentEffect::bindChildren is for use with components that don't provide appropriate high-level methods with signal support. Like with using ComponentEffect for component properties, the use of ComponentEffect in this case does not make other methods throw BindingActiveException
Reactive callbacks:
Some components have configuration in the form of a callback that is used to provide some configuration on demand. This pattern is most common in components that implement HasItems but it can also be seen e.g. with the highlight condition in RouterLink. If any such callback reads a signal value, then it the callback should automatically run again and the outcome applied whenever the signal value changes:
Simple cases like the router link highlight condition can be handled with some additional API in SignalPropertySupport. While out of scope for this PRD, extra care is needed for cases with lazy loaded items to ensure listeners are cleared when an item is no longer in the active range and also to avoid inflating memory use for the most common case with callbacks that don't read any signal value.
This mechanism should not be used for callbacks that are used to handle events – it's not expected that an event handler runs again unless the actual event is triggered again.
Other examples of methods in components that follow this pattern and should support signal binding:
See the "Reactive Callback" column in the full list of methods to be modified.
Selective rendering:
It shall be possible to dynamically render a single component out of a components option list based on a signal's value. A value can be of arbitrary type depending to a toggle condition, e.g. window resize, user permissions, edit mode, loading state etc.
The binding shall be done through the new helper ComponentToggle component as shown below (window width case, lazy-created component instances):
// widthSignal value can listen for resize event in Page::addBrowserWindowResizeListenerComponentToggle<Integer> toggle = newComponentToggle<>(widthSignal);
layout.add(toggle);
toggle.addExclusive(BigComponent::new, width -> width > 1000);
toggle.addExclusive(MediumComponent::new, width -> width > 500);
toggle.addFallback(SmallComponent::new);
ComponentToggle picks a single component out of lazy-creating component instances (as above) or a set of existing instances:
ComponentToggle does not have a client part, semantically similar to Composite and thus shall be placed into the Flow core module. It has no tag in the DOM. Due to these complexities, it's better to add the base feature into framework first that then makes adding ComponentToggle easier, see Create a ConditionalComponent flow#22031.
Multi-User Collaboration
Signals API is collaborative-ready by design within same JVM, but an extra validation is needed to test various scenarios like chat/message list, concurrent state modification, collaborative form editing.
Validation, DX and use cases under consideration
Greenfield validation of the new API should be run. For this, a list of use cases has been defined and we shall review and improve the generated code then answer ourselves the following:
are Signals best way to implement the use case and shall we keep considering that use case,
is the use case doable with the API that we plan to complete within a project, discuss and create new API requests otherwise,
does the code look useful, simpler than the classical approach and performant, make changes to the design or create bugs otherwise.
In addition to that, we shall aim to run DX tests (internal and, if possible, external) to validate how much Signals are appropriate for the new development and converting existing app.
Risks, limitations and breaking changes
Risks
Besides the requirements listed above, the goal of the project is to validate the new API against real-world use cases. Thus, the scope and design may change as we advance with development and validation. We should start testing the API as soon as possible to have time to change/supplement API accordingly. The testing plan/validation guidelines are given as a separate section.
Limitations
We recommend to use Signals for cases where a state change finally ends up on the UI. We are not considering cases where Signals are used outside a UI context, i.e. in the pure backend tasks, though such cases may work fine and give some benefits.
Breaking changes
Breaking changes in Signal and related classes in core are possible as the API is under the feature flag. Breaking changes in existing core or components API should be avoided.
Out of scope of 25.1, but must be considered later
Helpers for debugging Signals
Binding to items in HasItems / DataProvider components
Router integration
Clustering
Materials
No response
Metrics
No response
Pre-implementation checklist
Estimated (estimate entered into Estimate custom field)
Product Manager sign-off (Artur)
Engineering Manager sign-off (Mikhail)
Pre-release checklist
Documented (link to documentation provided in sub-issue or comment)
Description
Complete the Signals API and make it production-ready way of building reactive UI with Java and Vaadin by:
Tier
Free
License
Apache 2.0
Motivation
Background
Vaadin 24.8, 24.9 have shipped the base Signals class hierarchy and helpers in
ComponentEffect.Vaadin 25.0 has added
Element-level binding methods and helpers inElementEffect.Vaadin 25.1 now aims to fully integrate
SignalAPI into Vaadin Flow Components, complete the core API, make Signal classes serializable and conclude the overall design for the reactive UI state management, so that developers can use this way of building UI as a primary.Problem
Overall problem: It is common in Vaadin to explicitly handle UI state change by users in the event listeners, maintain clean ups and set the UI state explicitly through the component's methods. This may lead to a boiler-plate code that makes the view classes be unreadable and hard-maintained. Signals on the other hand may simplify the UI development by implicitly handling the state change no matter what kind of source did this - single user event, multi-user events or asynchronous background event, and implicitly handling the component UI attached/detached state.
Another frequent pattern is to make components be the Java class fields in "view" Java classes, where they are accessed along the class in different places, e.g. from the listeners of another component, form binding codes or even from outer layout Java class. Signals can help separating the "state" from the components, leaving only static initialization (e.g. constant column settings) from the dynamically changed content. This may relief the need of component class fields, but making them state fields instead, whereas the component declaration can be local method-scoped.
The need of using
UI.access()may bloat the code and requires a knowledge that this method exists and how to use it. Signals incapsulate this detail and basically use it under the hood, so the client code can just update the signal value and async updates work (of course the app needs to enable Push async updates).Finally, whereas the
Signaland related classes are available, it's not yet possible to code the reactive UIs using signals and without using existing low-level Element API, explicit effect functions and definingaddValueChangeListener(), nor there is a way of binding signals to a form.Solution
Signals integration to Vaadin Components would include:
Componentclass and mix-in interfaces for commonly used component properties (visible, enabled, value)setHelperText)This in the end means the component provide bind methods for the component features for the following kind of bindings
and the
BinderAPI is adjusted/extended so it's features can useSignaland benefit from two-way bindings.As an example of how the code may look like when using signals, guess the products price calculator add VAT to the base price and updates all product cards:
Example with the value change listeners:
Same example with signals:
Requirements
Common signal binding methods for components:
ComponentandElementclasses shall allow visibility binding to a boolean signal by usingvoid bindVisible(Signal<Boolean> visibleSignal)new methods.Componentdelegates toElementand has the same contractHasEnabledmix-in andElementclass shall allow enabling/disabling them with a boolean signal by usingvoid bindEnabled(Signal<Boolean> enabledSignal)new methods.HasEnableddelegates toElementand has the same contractHasTextmix-in interface shall allow binding their text to a signal by usingvoid bindText(Signal<String> textSignal)new method. This should delegate to the same existing method inElementnew Span(textSignal)HasValue<T>mix-in interface andElementshall allow a signal binding to their value by usingvoid bindValue(WritableSignal<T> valueSignal)new methods. Note that this is a two-way binding that also updates the signal value when aValueChangeEventis fired.Component-specific signal bindings:
In addition to the above common cases in components, there are plenty of other more specific methods that components share thanks to mix-in interfaces or in the standalone flow component classes. For those cases the components shall allow the signal binding through the newly added methods:
One-way binding:
A regular one-way bindings for a commonly updated configurations, that is not covered by text, enabled and visibility bindings, some examples:
HasTheme::bindThemeName(String, Signal<Boolean>),HasHelper::bindHelperText(Signal<String>),CheckboxGroup::bindRequired(Signal<Boolean>).See the full list for all one-way binding cases.
Two-way binding:
Two-way bindings are most common for
setValuefor components that implementHasValue(see next section forHasValue). The pattern is also applicable for some other cases that can be directly updated by the user such as whether aDetailspanel is opened, some examples:Details::setOpened(boolean),AppLayout::setDrawerOpened(boolean),Checkbox::setIndeterminate(boolean),MenuItemBase::setChecked(boolean),Crud::setDirty(boolean).See the full list for all two-way binding cases.
Other binding cases:
Other binding methods for component-specific state less common than visible, enable, text and value, e.g.
UI::setLocale(Locale),new AvatarGroup(AvatarGroupItem[]),new VerticalLayout(Component[])etc. See the sub-issue for the full list.Form binding and HasValue:
Small adjustments should be done to the current
BinderandHasValueAPIs, that would make the most common cases possible for reactive form binding and input fields binding with signals. Designing a new signal-native form binding solution to replaceBinderis out of scope of this PRD, if even decided.Signal with the bean/record to edit:
It should be possible to bind a bean signal to the form with a help of
ComponentEffectand this should be documented, no new API is added:Cross-field validation:
Field-level validator callbacks should be run inside a signal effect so that changes to any signal used by the validator will be detected and trigger running the validator again. Practical cross-field validation furthermore requires that the value of other bindings are available as signals, by adding a signal-aware value getter to
Binding:Reacting to validation status changes:
Bindershould provide a readonly signal that contains the latestBinderValidationStatusstate. The signal value should be updated whenever aBinderValidationStatusHandlerwould have been notified. This should work the same regardless of whether a status handler is explicitly configured or if the default that delegates tohandleBinderValidationStatusis in use.Updating other parts of the UI based on the bound bean:
We don't add any API to
Binderto simplify non-field cases that might now be handled usingReadOnlyHasValue. Instead, we assume there's already a signal containing the currently edited bean and recommend binding directly to that signal. If such a signal doesn't exist, then the first step is to add one.Two-way field bindings:
Binderis overkill for simple cases such as a standalone search field. Instead, the natural approach would be to bind a signal directly to the value of an input field. This kind of binding would be most natural as a two-way binding so that the developer doesn't have to do a React-style value change listener that only updates the signal value.Iteration:
ListSignal(or a computed signal with the same value type). To help implement this pattern,HasComponentsinterface shall provide avoid bindComponentChildrenmethod along with a similarbindElementChildrenmethod inElement, that takes the bound list signal and the mapping function between signal value and rendered component instance. It's under question what signal type and what generic type is better to use for the method signature, it should be decided during implementation and API validation, but the general considerations are:ReferenceSignalandValueSignaldepending on the need of concurrent modificationListSignalas well asSignal<List<S extends WritableSignal>>orSignal<List<ValueSignal<T>>becauseListSignaluses JSON representation for the value and that is not necessary in non-concurrent context.bindTextare mutually exclusive so that each one throws aBindingActiveExceptionif any binding is present. While bound, methods for appending or removing children also throw, as doesremoveFromParentfor any child that is attached through a bindingComponentEffect::bindChildrenis for use with components that don't provide appropriate high-level methods with signal support. Like with usingComponentEffectfor component properties, the use ofComponentEffectin this case does not make other methods throwBindingActiveExceptionReactive callbacks:
Some components have configuration in the form of a callback that is used to provide some configuration on demand. This pattern is most common in components that implement
HasItemsbut it can also be seen e.g. with the highlight condition inRouterLink. If any such callback reads a signal value, then it the callback should automatically run again and the outcome applied whenever the signal value changes:Simple cases like the router link highlight condition can be handled with some additional API in
SignalPropertySupport. While out of scope for this PRD, extra care is needed for cases with lazy loaded items to ensure listeners are cleared when an item is no longer in the active range and also to avoid inflating memory use for the most common case with callbacks that don't read any signal value.This mechanism should not be used for callbacks that are used to handle events – it's not expected that an event handler runs again unless the actual event is triggered again.
Other examples of methods in components that follow this pattern and should support signal binding:
Grid::setDropFilter(SerializablePredicate<T> dropFilter)GridContextMenu::setDynamicContentHandler(SerializablePredicate<T> dynamicContentHandler)GridPro.EditColumn::setCellEditableProvider(SerializablePredicate<T> cellEditableProvider)Select::setItemEnabledProvider(SerializablePredicate<T> itemEnabledProvider)See the "Reactive Callback" column in the full list of methods to be modified.
Selective rendering:
It shall be possible to dynamically render a single component out of a components option list based on a signal's value. A value can be of arbitrary type depending to a toggle condition, e.g. window resize, user permissions, edit mode, loading state etc.
The binding shall be done through the new helper
ComponentTogglecomponent as shown below (window width case, lazy-created component instances):ComponentTogglepicks a single component out of lazy-creating component instances (as above) or a set of existing instances:ComponentToggledoes not have a client part, semantically similar toCompositeand thus shall be placed into the Flow core module. It has no tag in the DOM. Due to these complexities, it's better to add the base feature into framework first that then makes addingComponentToggleeasier, see Create a ConditionalComponent flow#22031.Multi-User Collaboration
Validation, DX and use cases under consideration
Greenfield validation of the new API should be run. For this, a list of use cases has been defined and we shall review and improve the generated code then answer ourselves the following:
In addition to that, we shall aim to run DX tests (internal and, if possible, external) to validate how much Signals are appropriate for the new development and converting existing app.
Risks, limitations and breaking changes
Risks
Besides the requirements listed above, the goal of the project is to validate the new API against real-world use cases. Thus, the scope and design may change as we advance with development and validation. We should start testing the API as soon as possible to have time to change/supplement API accordingly. The testing plan/validation guidelines are given as a separate section.
Limitations
We recommend to use Signals for cases where a state change finally ends up on the UI. We are not considering cases where Signals are used outside a UI context, i.e. in the pure backend tasks, though such cases may work fine and give some benefits.
Breaking changes
Breaking changes in
Signaland related classes in core are possible as the API is under the feature flag. Breaking changes in existing core or components API should be avoided.Out of scope of 25.1, but must be considered later
HasItems/DataProvidercomponentsMaterials
No response
Metrics
No response
Pre-implementation checklist
Pre-release checklist
Security review
None