Trying out signals in Flow with Vaadin 24.8

One very exciting new feature in Vaadin 24.8 is the preview of signals for reactive UI state management. What makes this feature so revolutionary is that it introduces full reactive UI state management to Flow. You can set up any component instance to configure itself based on some signal values and then the component automatically stays in sync with the signals.

Right now is an excellent opportunity for the eager ones in the crowd to get some first impressions and help us move this initiative in the right direction. We really want to make sure we got this one right before we remove the feature flag and declare it ready for general use. Just keep in mind that there are some part that we know are still lacking (more about those below) and that will need to be covered before signals can be the default solution for all your UI state management needs.

Hello signal world

You can try signals with Flow in any application that uses Vaadin 24.8, e.g. from https://start.vaadin.com/. You need to enable the “Flow Full-stack Signals” feature flag either through the Features section in the right side panel that is enabled from the }> development menu when running the application, or by manually adding com.vaadin.experimental.flowFullstackSignals=true to src/main/resources/vaadin-featureflags.properties.

You are now ready to create a simple view with a single piece of state: a counter tracking how many times a button has been clicked:

@Route @Menu @PermitAll
public class Signals extends VerticalLayout {
  private final NumberSignal count = new NumberSignal();

  public Signals() {
    Button button = new Button();
    button.addClickListener(event -> count.incrementBy(1));

    ComponentEffect.format(button, Button::setText, "Click count: %.0f", count);

    add(button);
  }
}

All of the magic is in the ComponentEffect helper method that sets things up to do something equivalent to button.setText("Click count: %.0f".formatted(count.value())) whenever the value of the count signal changes.

The real power comes from the fact that you can combine signals and component effects in arbitrary ways. As another simple example, you can create a computed signal that checks whether the click count is even or odd and then shows and hides a Span based on this. The magic is in the fact that you just use the same state in multiple places without having to make any changes to the click listener that updates that state.

Signal<Boolean> isEven = Signal.computed(() -> count.valueAsInt() % 2 == 0);
Span evenSpan = new Span("Count is even");
ComponentEffect.bind(evenSpan, isEven, Span::setVisible);
add(evenSpan);

Two-way bindings

Signals also shine in cases where data is bound both ways, i.e. anything related to forms. This is one area where we will add some helper functionality to the framework but you can also do things relatively easily with just the low-level building blocks. The key trick for now is to avoid triggering the infinite loop detection by trying to change the signal value only if the value has actually changed.

ValueSignal<String> text = new ValueSignal<>("Initial value");
TextField field = new TextField("Enter some text");
field.addValueChangeListener(event -> {
  if (!Objects.equals(event.getValue(), text.peek())) {
    text.value(event.getValue());
  }
});
ComponentEffect.bind(field, text, TextField::setValue);

Span valueSpan = new Span();
ComponentEffect.format(valueSpan, Span::setText, "Value is: %s", text);
Button setValue = new Button("Set value", click -> text.value("Set from button"));
add(field, valueSpan, setValue);

One very powerful example of what we can do with this is automatic cross-field validation. As a completely made-up use case, let’s say that if the click count is even, then the number of characters in the text field also have to be even. There’s a little bit of boilerplate code due to directly manipulating the field status rather than using Binder. The key thing to notice is the way the validator logic will automatically run again whenever either of the two dependencies change.

field.setManualValidation(true);
ComponentEffect.effect(field, () -> {
  boolean evenClick = isEven.value();
  boolean evenText = text.value().length() % 2 == 0;

  field.setInvalid(evenClick == evenText);
  field.setErrorMessage("Length must be " + (evenClick ? "even" : "odd"));
});

Collaboration

Last year when we introduced “full-stack signals” for Hilla (still in preview) there was lots of focus on collaboration and live updates from the server. You can certainly also use signals in Flow for the same purpose even though that’s just cherry on the top rather than the main thing in this case.

Component updates based on signal changes are automatically synchronized using UI.access behind the scenes whenever necessary. This means that you can share signal instances for any case where you want to share UI state between users and everything will just automatically work.

The simplest way of testing this out is to change the count instance field into a static field so that all instances of the view use the same signal instance. We also need to enable server push by adding the @Push annotation to the Application class.

private static final NumberSignal count = new NumberSignal();

FAQ

ComponentEffect use looks verbose. Couldn’t that functionality be included in component classes?

We should add short hand APIs to enable things like e.g. span.setVisible(someSignal) or span.setText("Value is: %s", text). We expect to do that once we have gained some confidence in the core idea.

How can I populate the UI based on a list?

There’s a ListSignal class of managing list data in an efficient way. We plan to introduce a helper along the lines of ComponentEffect.bindChildren(parent, listSignal, childSignal -> createComponent(childSignal)) that would take care of attaching, detaching and moving child components efficiently.

Until that, you can use this quite inefficient solution that re-creates all components for any structural change:

ComponentEffect.effect(parent, () -> {
  parent.removeAll();
  listSignal.value().forEach(childSignal -> parent.add(createComponent(childSignal)));
});

Why do I get an error about read-only transactions?

To help protect against infinite update loops, you’re not allowed to update any signal from inside an effect or computed callback since they are run in reaction to some other signal changing. This is implemented as a “read-only transaction” which leads to that somewhat confusing error message.

This typically happens when you create a two-way binding since changing the signal value will trigger the effect which in turn triggers the value change listener that tries to update the signal value. We plan to provide a better way of creating two-way bindings but as a workaround for now, you can use the same Objects.equals condition in the value change listener that I showed in one of the examples.

Fixing the problem in the general case usually requires you to re-think what makes up the “primary” or “authoritative” state from which everything else can be derived and then introducing computed signals for anything that can be derived.

How does this relate to Hilla?

The full-stack signal implementation in Hilla uses Java types in the com.vaadin.hilla.signals package. These types are not compatible with the com.vaadin.signals types that are used with Flow. We will update Hilla to use com.vaadin.signals in the future.

This looks cool. Where can I find out more?

Just ask here.

You can also have a look at the reference documentation for a more comprehensive overview of all the key features.

(This one probably goes in the category of “frequently wished questions”, but I just needed some place to put that link.)

5 Likes

Wanting to give this a quick try, I noticed that none of the classes related to Signals are marked as Serializable. Is this going to be added in the next version?

Optimistic serialization of session DCE0EAD528E5A37BD2310D56B2D47E91 with distributed key 4e56738c-e77e-4884-b83e-bf51f5100092 failed, some attribute is not serializable. Giving up immediately since the error is not recoverable

java.io.NotSerializableException: com.vaadin.flow.component.ComponentEffect
	at java.base/java.io.ObjectOutputStream.writeObject0(ObjectOutputStream.java:1187)

We’re planning dedicated clustering support for sharing UI state between multiple servers. We’ll tackle serialization at the same time.

2 Likes

Intersting stuff, I have made a quick use of it in my test application, see the “Load & performance test”:

The application is demonstrating a multilevel 1-N domain architeture:
Company → Department → Employee → Item

the specific code is in:
dk.netbizz.vaadin.tenantcompany.ui.view.TenantDepartmentGrid
dk.netbizz.vaadin.tenantcompany.ui.view.TenantDepartmentGrid
dk.netbizz.vaadin.user.ui.view.EmployeeGrid
dk.netbizz.vaadin.item.ui.view.ItemGrid

This requires that updating the selected Company in the Company grid affects the list shown in the Department grid and so on.
When the Department grid is refreshed with new data due to selecting a new Company in the Company grid, the Department grid has to be deselected.
This deselection of the Department grid must be signalled to the Employee grid etc. This is usually done by setting the selected department-id to null.

So I have a couple of questions for better understanding.
First, why can’t a ValueSignal be set to null? Do I need to make a compound object/record?
E.g:

record NullableId (Integer id, boolean isNull) { }

and make a
ValueSignal <NullableId> companyId;

Next question
How do I implement some kind of chaining of signals, since I can’t update a signal inside a ComponentEffect?
The exception is as documented, but how then can I do this?
See line 54 in dk.netbizz.vaadin.tenantcompany.ui.view.TenantDepartmentGrid:

ComponentEffect.effect(this, () -> {
    setTenantCompanyId(SignalHost.signalHostInstance().getSignal(SignalHost.COMPANY_ID).value());
    // departmentIdSignal.value(0);    // TODO - This fails with:   java.lang.IllegalStateException: Cannot make changes in a read-only transaction.  Also, this should be set to null
});

Final question and comment.
I consider the main advantage of signals to be decoupling, much like a classic publish/subscribe eventbus, and it does provide less coupling for my usecase.
But that begs the question if there is some mechanism or perhaps a standard pattern for sharing the signals, I chose to make a singleton with a Map as a simple solution.

So I know it’s an early stage and my usage is not really a full stack usage, more like a UI eventbus, but with less registration housekeeping and I’m curious to exploring it.

Br Mikael

1 Like

Thank you for your exploration and the thoughtful questions you’re bringing!

This is supposed to work but it seems like you discovered a bug. I verified and filed it in Signal effect does not trigger when value changes to null · Issue #21778 · vaadin/flow · GitHub.

This is interesting because there are two pieces of state that are in one way separate and in one way dependent. The department id is a standalone variable but it should be cleared whenever the company id changes.

I’m aware of multiple ways of handling this kind of case:

  1. The most common solution with other UI frameworks that have similar state management mechanisms is to change the abstraction level. With imagined deep linking URLs, there is a transition from /companies/1/departments/1 to just /companies/2. This means that the UI event listener should not only change the company id to 2 but also clear the department id at the same time. It might make sense to treat the “navigation state” of the view as a single signal that contains a record with both company id and (optional) department id rather than as two distinct signals. This in turn points to one of the reasons why two-way data binding has somewhat fallen out of fashion: it’s not enough to just have a two-way binding between the company selection component and the company id signal but you would instead need an explicit event listener on the company selection change.
  2. Another approach is to adjust the semantics of department selection so that the user is actually selecting a [company id, department id] tuple. The effectively selected department is then a computed signal that listens to both the company id selection and the [company id, department id] tuple so that the selected department id is used only if the company id in the tuple matches the selected company id.
  3. Accept that this case is an exception to the general rule about signal updates from inside effects. There should not be any risk of infinite loops in this case since there’s no way that department selection might even indirectly affect company selection. It’s thus probably safe to just use runWithoutTransaction in the effect to tell the framework to not be paranoid about infinite loops.
  4. We might want to add something similar to the “linked signal” concept that was recently added to Angular.

I would personally lean towards the first option and I have been considering some ideas for how to integrate signals with routing to give you that deep linking structure “for free”. But this is certainly one of the cases where I’m not yet sure about which approach to suggest. We just need to collect more practical experience.

Whenever we talk about sharing, there’s also a question about the scope of that sharing. Some state should be shared globally between all UI instances for all users. Some between all UI instances that have the same business entity open. Some between all UI instances of a single user. Some between different parts within the same UI instance. Some only between components in the same view instance.

There is built-in support for the singleton with a map pattern through SignalFactory.IN_MEMORY_SHARED. State isolated to a view instance is also natural to have as instance fields of the view component. For anything in between, I’m leaning towards relying on Spring / CDI scopes with signal instances in managed beans with a suitable scope.

You could note that there’s a known issue with using a singleton scope for a single global signal instance due to the way the scoped bean will be initialized before VaadinService initializes the signal environment.

I expect that the biggest impact of signals in Flow will be exactly for this kind of usage. What I really like from using Lit or React is the improved maintainablitly when no UI event listener is directly touching any component instance but there’s instead a strict separation into UI event → state change → component update. This has been possible in Flow since forever but it has required lots of boilerplate code to manage listeners to the point where most simpler applications choose a more direct architecture.

The possibliity of also seamlessly sharing UI state between users and even across a cluster is just cherry on top.

1 Like

Thanks, good points and answers.
Just for an update, this seems to work:

public class TenantCompanyGrid extends GenericGridProEditView {

private ValueSignal<Integer> companyIdSignal = new ValueSignal<>(0);

public TenantCompanyGrid() {
    ...
    SignalHost.signalHostInstance().addSignal(SignalHost.COMPANY_ID, companyIdSignal);
}

…
}

public class TenantDepartmentGrid extends GenericGridProEditView {

private Integer tenantCompanyId = null;
private ValueSignal<Integer> departmentIdSignal= new ValueSignal<>(0);

public TenantDepartmentGrid() {
   ...
    SignalHost.signalHostInstance().addSignal(SignalHost.DEPARTMENT_ID, departmentIdSignal);
    ComponentEffect.effect(this, () -> {
        Signal.runWithoutTransaction(() -> {
            setTenantCompanyId(SignalHost.signalHostInstance().getSignal(SignalHost.COMPANY_ID).value());
            departmentIdSignal.value(null);
        });
    });
}

So when discussing the options for chaining of signals:

  1. Combining it with deep linking makes sense, but beware that it can go to many more levels.
  2. I don’t like that, it can become an annoying overhead if you fetch deep nested data every time another top entity is selected - btw. that is why I don’t subscribe to pure DDD in Spring Data.
  3. That seems to be a good workaround in some cases - my repo is updated.
  4. I don’t know that, but it “sounds good”, so to speak.

A minor detail but I would recommend doing as little as possible in runWithoutTransaction. In this case, it probably means that setTenantCompanyId could be moved out of that block.

I envision an automatic implicit transaction to give repeatable reads while a UI is locked but that would not be effective inside runWithoutTransaction.

1 Like

Based on the examples here and in the documentation it’s very hard to see for what to use Signals.
It would be great to have examples that show the old way (whatever that is) and how to do it with Signals.

2 Likes

The basic idea is to replace any instance field holding a component instance or state variable so that you instead have a signal containing that state as the instance field and the component is referenced only during initialization.

Taking the very simple click count button as the example, you would go from this

public class NoSignals extends VerticalLayout {
  private final Button button = new Button();
  private int count;

  public NoSignals() {
    button.addClickListener(event -> setCount(count + 1));

    setCount(0);
    add(button);
  }

  private void setCount(int count) {
    this.count = count;
    button.setText("Click count: " + count);
  }
}

to this

public class Signals extends VerticalLayout {
  private final NumberSignal count = new NumberSignal();

  public Signals() {
    Button button = new Button();
    button.addClickListener(event -> count.incrementBy(1));

    ComponentEffect.format(button, Button::setText, "Click count: %.0f", count);

    add(button);
  }
}

This has multiple benefits:

  1. Both initialization and incremental updates are combined into the same binding statement rather than calling setCount separately in the constructor for initialization and in the event listener for incremental updates.
  2. You cannot accidentally do this.count++; and forgetting to call setCount.
  3. It’s easier to get an overview of what might happen to the button when everything related to it is concentrated in one place rather then spread out in various set or update methods (though it gets more difficult to see what the count state is used for).
  4. In cases with multiple state variables affecting the same component state, you avoid the risk of inconsistencies between the setters or cases where the order of calling the setters might have an impact.
  5. There’s less code overall since you don’t need the helper method.
  6. Sharing mutable static between different components gets easier since you can just share the signal instance - even across UI instances and user sessions if you want.
  7. Not relevant in this case, but cross-field validation in forms becomes trivial when the validation rule for each field is automatically evaluated whenever any of its dependencies change rather than now when it’s automatically triggered only when the field’s own value changes.
  8. Potentially in the future, we could optimize server-side memory consumption by having only one instance of the Signals class and instead making the count signal hold different values for different users. This would have to be opt-in but it could even be stretched to the point where the count value is passed back and forth in requests and responses (optionally with some cryptography to ensure confidentiality and/or integrity) so that the server-side logic would be fully stateless.

Most of these benefits are also possible by using something like an explicit MVP or MVVM pattern for the view but then you end up with even more boilerplate code. From that point of view, signals can be seen as a viewmodel that is designed to simplify updating the UI in reaction to changes.

2 Likes

I see this example but still don’t see how this could make my code easier.
I rarely have these use cases.
Do you have any more real-world use-cases?

1 Like

Upon closer investigation, the issue that I verified was a regression from a change made just yesterday since I was testing with the latest snapshot rather than the 24.8.0 release.

If you’re using 24.8.0, then the null issue would need some furhter investigation since this smiple example seems to work as expected with 24.8.0:

ValueSignal<String> signal = new ValueSignal<>("initial");

Span span = new Span();
ComponentEffect.bind(span, signal, Span::setText);

Button button = new Button("Set to null", click -> signal.value(null));
add(span, button);
1 Like

It seems I can’t recreate it either, perhaps I made a simple mistake, it was some initial test code, so I don’t have it included and now I also get an event for null.
As you can see from the example above, I’m using null and it works here too:

    ComponentEffect.effect(this, () -> {
        Signal.runWithoutTransaction(() -> {
            setTenantCompanyId(SignalHost.signalHostInstance().getSignal(SignalHost.COMPANY_ID).value());
            departmentIdSignal.value(null);
        });
    });

If I come across it again, I will be sure to make a demo.
Sorry for the trouble.

1 Like

A pattern that we often use to decouple UI components is this:

Component broadcasting state changes:

public class CountryCrud extends Div {

  public void createCrud() {
    ....
    crud.addSaveListener(
        event ->
            ComponentUtil.fireEvent(UI.getCurrent(), new CountrySelectionChanged(crud)));
  }
}

Component reacting to state changes, in the same page but a different section (we have huge forms)

public class CaseFormLayout extends FormLayout {

  protected final List<Registration> eventListenerRegistrations = new ArrayList<>();

  @Override
  protected void onAttach(AttachEvent attachEvent) {
    eventListenerRegistrations.add(
        ComponentUtil.addListener(
            getCurrent(), CountrySelectionChanged.class, this::onCountrySelectionChange));
  }

  @Override
  protected void onDetach(DetachEvent detachEvent) {
    eventListenerRegistrations.forEach(Registration::remove);
  }

Am i correct in understanding that Signals could be used instead here ?

@SimonMartinelli: One use case that I can think of is maintaining the number of unread messages/notications, and using that state in various places across the app, for example a number badge in some button, unread state in some notifcation popup, and then the unread state in the list of messages.

1 Like

This is indeed a case where signals could help by passing the same signal instance to both components. In addition to the small code simplifications, a bigger benefit might be that by making it explicit which components use which signal instances, you’re effectively also documenting the component’s dependencies more explicitly than through the class literals passed to addListener and fireEvent.

Publishing a change:

public class CountryCrud extends Div {

  public void createCrud() {
    ....
    crud.addSaveListener(
        event -> countrySelectionsignal.value(selectedCountry));
  }
}

Reacting to the change:

public class CaseFormLayout extends FormLayout {
  public CaseFormLayout() {
    ComponentEffect.bind(this, countrySelectionsignal, CaseFormLayout::setSelectedCountry);
    // Simplified once we add some helper APIs to Component
    // bind(countrySelectionsignal, this::setSelectedCountry);
  }
}

It requires a slightly different mindset since you’re sharing state rather than emitting state change events but the big benefit is that you don’t need to manually manage the listeners.

1 Like

Indeed, i would consider this to be a clearer code pattern and a good use case for Signals. Thanks for clarifying.

I put together a slightly more enterprisey example based on managing order lines in an order management system for spaceship parts.

This consists of one component for the main form and a helper component for each line.

The main component uses a list signal of immutable order line records. It also has a copule of computed signals for aggregating a summary of all the lines. A new order line is added just by inserting a new instance into the list signal and then the reactive binding takes care of attaching the line editor component in the right place.

public class Orders extends VerticalLayout {
    private final ListSignal<OrderLine> lines = new ListSignal<>(OrderLine.class);

    public Orders() {
        Signal<Integer> itemCount = lines.map(list -> list.stream()
                .mapToInt(line -> line.value().quantity()).sum());
        Signal<Double> totalPrice = lines.map(list -> list.stream()
                .mapToDouble(line -> line.value().price()).sum());

        Div lineWrapper = new Div();
        FutureComponentEffect.bindChildren(lineWrapper, lines, OrderLineComponent::new);

        Span summary = new Span();
        ComponentEffect.format(summary, Span::setText,
                "%d items for a price of $%.2f", itemCount, totalPrice);

        ComboBox<Product> addBox = new ComboBox<>("Add product", products);
        addBox.setItemLabelGenerator(Product::name);
        addBox.addValueChangeListener(change -> {
            Product product = change.getValue();
            if (product != null) {
                lines.insertLast(new OrderLine(product, 1));
                change.getSource().setValue(null);
            }
        });

        add(lineWrapper, addBox, summary);
    }
}

The order line editor has a bunch of computed signals for the different values it shows. Just like for adding lines, all that is needed is to remove the entry from the state and then the reactive binding removes the right component from the tree. Also note that the parent component automatically gets updated when the child updates the quantity without needing any explicit events or such.

private static class OrderLineComponent extends HorizontalLayout {
    public OrderLineComponent(ValueSignal<OrderLine> signal) {
        Signal<String> productName = signal.map(OrderLine::productName);
        Signal<Integer> quantity = signal.map(OrderLine::quantity);
        Signal<Double> unitPrice = signal.map(OrderLine::unitPrice);
        Signal<Double> price = signal.map(OrderLine::price);

        getStyle().setAlignItems(AlignItems.BASELINE);

        Span nameSpan = new Span();
        ComponentEffect.bind(nameSpan, productName, Span::setText);
        nameSpan.setWidth("160px");

        IntegerField quantityField = new IntegerField(change -> {
            Integer value = change.getValue();
            if (signal.peek().quantity != value) {
                signal.update(line -> line.withQuantity(value));
            }
        });
        quantityField.setWidth("110px");
        quantityField.setStepButtonsVisible(true);
        ComponentEffect.bind(quantityField, quantity,
                IntegerField::setValue);

        Span priceSpan = new Span();
        ComponentEffect.format(priceSpan, Span::setText, "x $%.2f = $%.2f",
                unitPrice, price);
        priceSpan.setWidth("170px");

        Button removeButton = new Button(VaadinIcon.TRASH.create(),
                click -> signal.asNode().value().parent()
                        .removeChild(signal.asNode()));

        add(nameSpan, quantityField, priceSpan, removeButton);
    }
}

Removing is a bit inconvenient with falling back to the low-level asNode() mechanism for finding the parent. We should maybe add a valueSignal.removeFromParent() helper for this case. There’s also a somewhat inconvenient if in the value change listener to triggering the infinite loop detection. We should do something about that as well but I haven’t yet figured out what would be a good approach.

The last thing to mention is that all the static ComponentEffect method calls should become instance methods in the components in the future, or in some cases even shorthand constructors.

Hi again

I’m really caught by the idea of using Signals for less dependency in my domain specific code, so I’m trying out different ideas.

Quoting you Leif:
" There is built-in support for the singleton with a map pattern through SignalFactory.IN_MEMORY_SHARED. State isolated to a view instance is also natural to have as instance fields of the view component. For anything in between, I’m leaning towards relying on Spring / CDI scopes with signal instances in managed beans with a suitable scope.

You could note that there’s a known issue with using a singleton scope for a single global signal instance due to the way the scoped bean will be initialized before VaadinService initializes the signal environment."

I felt like doing something like this to have a SignalHost Bean available per user session:

@VaadinSessionScope
public class SignalHost {

    public static final String COMPANY_ID = "companyId";
    public static final String DEPARTMENT_ID = "departmentId";
    public static final String EMPLOYEE_ID = "employeeId";
    public static final String ITEM_ID = "itemId";

    private Map<String, ValueSignal<Integer>> signalMap = Collections.synchronizedMap(new HashMap<>());

    public SignalHost() {
        addSignal(COMPANY_ID, SignalFactory.IN_MEMORY_EXCLUSIVE.value(COMPANY_ID, null));
        addSignal(DEPARTMENT_ID, SignalFactory.IN_MEMORY_EXCLUSIVE.value(DEPARTMENT_ID, null));
        addSignal(EMPLOYEE_ID, SignalFactory.IN_MEMORY_EXCLUSIVE.value(EMPLOYEE_ID, null));
        addSignal(ITEM_ID, SignalFactory.IN_MEMORY_EXCLUSIVE.value(ITEM_ID, null));
    }

    public void addSignal(String name, ValueSignal<Integer> signal) {
        signalMap.put(name, signal);
    }

    public ValueSignal<Integer> getSignal(String key) {
        return signalMap.get(key);
    }

}

but it fails in the constructor with a null pointer, I guess that is what you warned about.
And since I can’t move it to the value-setting component either, without the same problem. and
I don’t see a proper method in the

VaadinServiceInitListener 

interface either , how then would you design a set of session-scoped signals for a best practice example. Am I too early or am I just flashing my ignorance :slight_smile:

Br Mikael

Thanks for the input @Leif and @Jouni1 I finally found a use to try Signals out

1 Like

That seems to be a different issue. Your NPE probably comes from SignalFactory.IN_MEMORY_EXCLUSIVE.value(COMPANY_ID, null) where Java chooses the overload where the second parameter is the value type. You should use something like SignalFactory.IN_MEMORY_EXCLUSIVE.value(COMPANY_ID, Intger.class) instead in cases where the value type cannot be derived from the initial value instance.

I also had to add @Service to the SignalHost class but that’s of course not related to signals.

Finally, I could point out that it’s redundant to directly use SignalFactory.IN_MEMORY_EXCLUSIVE in that way. IN_MEMORY_EXCLUSIVE mainly exists for use with generic methods that require an instance of SignalFactory. In a case like this, the code is probably clearer if you just do new ValueSignal<>(Integer.class) and the end result will be identical.

1 Like