Working with Signals and one object instanced from one Entity

I’m try work with Signals to keep UI components updated in a MainView. I have one app that use the AppLayout in MainView. This class received the AuthenticatdUser throut Spring Security login. This AuthenticatedUser are converted to my owm User Entity representation. The user can make changes in your owm profile, like updated email, or your profile picture. I wold like that when this changes (that works in another view) are commited, change the state of components in MainLayout that represent this infos. But, I can’t instance the ValueSignal with a object before inoke the construct of MainLayout, because we just have access to correct entity, after login pass parameter to MainLayout with AuthenticatedUser.

Any suggestions?

I’ve asked Claude for you because I was curious how well Claude understands Vaadin without Skills and MCP. Here’s what I got:

Good question, and the fix is mostly about where the signal lives. The signal should not be created by MainLayout. Put it in a session scoped Spring bean, then inject that bean into both the layout and the profile view.

Here is an answer you can post.


Move the signal out of the layout

A @VaadinSessionScope bean is created the first time somebody injects it. That happens when MainLayout is built, so after the login. At that moment AuthenticatedUser already works, and you can load the entity in the bean constructor.

@SpringComponent
@VaadinSessionScope
public class CurrentUser {

    private final ValueSignal<UserInfo> signal;

    public CurrentUser(AuthenticatedUser authenticatedUser, UserService userService) {
        this.signal = new ValueSignal<>(
            authenticatedUser.get()
                .map(u -> userService.toUserInfo(u))
                .orElse(null));
    }

    public Signal<UserInfo> get() {
        return signal.asReadonly();   // views can read
    }

    public void update(UserInfo info) {
        signal.set(info);             // only this class can write
    }
}

new ValueSignal<>() without an argument is also allowed and starts with null, so even a lazy load is no problem. You just guard for null when you read it.

Use it in MainLayout

public MainLayout(CurrentUser currentUser) {
    Signal<UserInfo> user = currentUser.get();

    Span name = new Span();
    name.bindText(user.map(u -> u == null ? "" : u.name()));

    Avatar avatar = new Avatar();
    Signal.effect(avatar, () -> {
        UserInfo u = user.get();
        avatar.setName(u == null ? null : u.name());
        avatar.setImage(u == null ? null : u.pictureUrl());
    });
    ...
}

bindText() and friends are the preferred way. Use Signal.effect(component, ...) only for properties that have no binding method, like the avatar image. The effect is tied to the component lifecycle, so no manual cleanup.

In the profile view

saveButton.addClickListener(e -> {
    UserInfo saved = userService.save(form.getValue());
    currentUser.update(saved);      // MainLayout updates itself
});

Three things I would add

  1. Do not put your JPA entity in the signal. Signals compare values with equals() and expect immutable values. A detached entity that you mutate in place will not trigger an update. Use a small record like record UserInfo(String name, String email, String pictureUrl) {}.
  2. If the same user has two browser tabs open, both share the session bean, but you need @Push so the other tab really repaints.
  3. If you are still on Vaadin 24.8 or 24.9, the API names are different: com.vaadin.signals.ValueSignal with value() instead of get()/set(), and ComponentEffect.effect(...) instead of Signal.effect(...). The pattern stays the same.

There is a nearly identical example in the docs under Read-Only Signal Views, which is a good link to give the person.