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
feat: add ListSignal section to the Signals documentation (#3844)
* initial version
* edit contents for better organization
* fix version badge
* Apply suggestions from code review
Co-authored-by: Luciano Vernaschi <luciano@cromoteca.com>
* use API in singular form
* add description for Operation return type
* Edited add text.
* More edits.
* Minor edit.
* Apply suggestions from code review
Co-authored-by: Luciano Vernaschi <luciano@cromoteca.com>
---------
Co-authored-by: Luciano Vernaschi <luciano@cromoteca.com>
Co-authored-by: Russell J.T. Dyer <6652767+russelljtdyer@users.noreply.github.com>
Co-authored-by: Russell JT Dyer <russelljtdyer@users.noreply.github.com>
When building a modern web application, you may often need to synchronize the state between the server and clients. You might want to notify all connected clients in a chat application when a new message is posted, or maybe visualize multiple users interacting while editing the same record in a form, or perhaps update clients when the status of an order changes in an e-commerce application. This becomes even trickier when you need these updates to be propagated to clients in real time. Fortunately, this is when full-stack signals can help.
10
+
When building a modern web application, you may often need to synchronize the state between the server and clients. You might want to notify all connected clients in a chat application when a new message is posted, or maybe visualize multiple users interacting while editing the same record in a form, or perhaps update clients when the status of an order changes in an e-commerce application. It becomes trickier when you need these updates to be propagated to clients in real time. Fortunately, full-stack signals can help.
11
11
12
12
A full-stack signal can be seen as a special data type that holds the shared state. It enables clients to subscribe to it for receiving real-time updates when the state changes. The state can be updated by any client, and the changes are automatically propagated to all other clients which are subscribed to the signal.
13
13
@@ -53,6 +53,7 @@ public class VoteService {
53
53
<1> Create an instance of a [classname]`ValueSignal` with an initial value of false.
54
54
<2> Return the instance of the [classname]`ValueSignal` from the [methodname]`startedSignal` method.
55
55
56
+
56
57
[CAUTION]
57
58
The server-side instance of a full-stack signal is meant to be created once and shared across all clients. Therefore, create the instance as a field in a service class, and return it from the methods. This way, all clients are able to subscribe to the same signal instance and receive real-time updates when the state changes.
58
59
@@ -97,17 +98,19 @@ export default function VoteView() {
97
98
98
99
99
100
[[available-full-stack-signal-types]]
100
-
== Available Full-stack Signal Types
101
+
== Available Full-Stack Signal Types
101
102
102
103
The full-stack signals are designed to be used in various scenarios. Based on the requirements, different types of full-stack signals are used. The server-side signal types are available in the `com.vaadin.hilla.signals` package. Their client-side counterparts are available in `@vaadin/hilla-react-signals`.
103
104
104
-
As this is currently under active development, more signal types are added with each new release. Currently available ones are: `ValueSignal`and `NumberSignal`. These are described in the following sections.
105
+
As this is currently under active development, more signal types are added with each new release. The currently available ones are [classname]`ValueSignal`, [classname]`NumberSignal`, and [classname]`ListSignal`. These are described in the following sub-sections.
105
106
106
107
107
108
[[value-signal]]
108
109
=== ValueSignal
109
110
110
-
The `ValueSignal<T>` is a full-stack signal that holds a single value of an arbitrary type. The type should necessarily be a JSON-serializable one that is supported by the Hilla framework. The following example demonstrates how to create and use a [classname]`ValueSignal` in a server-side service:
111
+
The `ValueSignal<T>` is a full-stack signal that holds a single value of an arbitrary type. It has to be a JSON-serializable type that's supported by the Hilla framework.
112
+
113
+
The following example demonstrates how to create and use a [classname]`ValueSignal` in a server-side service:
111
114
112
115
[source,java]
113
116
.`SomeService.java`
@@ -179,7 +182,7 @@ public class PersonService {
179
182
<3> The signal instance that holds the shared state of the person.
180
183
<4> The service method that returns the signal instance. The [classname]`@Nonnull` annotations are used to indicate that both the returned signal and its value may never be null. However, if the signal instance or its value might be null, you can remove the `@Nonnull` annotations.
181
184
182
-
Although the above example shows the usage of a record, you can also use classes with mutable properties. There aren't any technical limitations on this, as the wrapped value of the signal is always replaced with a new instance whenever an update is applied to the signals. However, the usage of immutable types is always preferred when dealing with share values. It helps to reduce the confusion and potential bugs that might arise from the shared mutable state.
185
+
Although the above example shows the usage of a record, you can also use classes with mutable properties. There aren't any technical limitations on this, as the wrapped value of the signal is always replaced with a new instance whenever an update is applied to the signals. However, the usage of immutable types is always preferred when dealing with share values. It helps to reduce confusion and potential bugs that might arise from the shared mutable state.
183
186
184
187
Having a [classname]`@BrowserCallable`-annotated service with a method that returns a [classname]`ValueSignal` instance similar to the above example, enables the client-side code to subscribe to it by calling the service method:
185
188
@@ -226,7 +229,7 @@ All signals have a `value` property that can be used to both set and read the va
226
229
227
230
A call to `cancel()` is not guaranteed always to be effective, as a succeeding operation might already be on its way to the server.
228
231
229
-
Operations such as `replace` and `update` are performing a "compare and set" on the server using the [methodname]`equals` method of the value type to compare the values. Thus, it's important to make sure the value type has a proper implementation of the [methodname]`equals` method.
232
+
Operations such as `replace` and `update` perform a "compare and set" on the server using the [methodname]`equals` method of the value type to compare the values. Thus, it's important to make sure the value type has a proper implementation of the [methodname]`equals` method.
<4> Decrease the value of the signal using the atomic [methodname]`incrementBy` operation and providing a negative value.
287
290
<5> Reset the value of the signal to `0` by assigning a new value to it.
288
291
289
-
The `incrementBy` operation is _incrementally atomic_, meaning it guarantees success by reading the current value and applying the increment on the value, atomically. Each operation builds on the previously accepted one, ensuring that `n` increments or decrements are always applied correctly -- even if there are multiple clients trying to update the value, concurrently.
292
+
The [methodname]`incrementBy` operation is _incrementally atomic_, meaning it guarantees success by reading the current value and applying the increment on the value, atomically. Each operation builds on the previously accepted one, ensuring that `n` increments or decrements are always applied correctly -- even if there are multiple clients trying to update the value, concurrently.
290
293
291
294
Since [classname]`NumberSignal` is a [classname]`ValueSignal` with the additional atomic operation of [methodname]`incrementBy`, it inherits all methods, such as [methodname]`replace` and [methodname]`update`, making those operations available when using a [classname]`NumberSignal`.
292
295
293
296
297
+
[[list-signal]]
298
+
[role="since:com.vaadin:vaadin@V24.6"]
299
+
=== ListSignal
300
+
301
+
The [classname]`ListSignal<T>` is a full-stack signal that holds a list of values of an arbitrary type. It has to be a JSON-serializable type that's supported by the Hilla framework.
302
+
303
+
The following example demonstrates how to create and use a [classname]`ListSignal` in a server-side service:
<2> The `value` property of the [classname]`ListSignal` holds the list of tasks. The length of the list is checked to display a message when there are no tasks.
378
+
<3> The `map` function is used to render the list of tasks.
379
+
<4> Add a new task to the list by calling the [methodname]`insertLast` method of the [classname]`ListSignal`.
380
+
381
+
Since the `todoItems` signal holds the shared list of tasks, any subscribed client to this signal receives real-time updates when the list changes. As a result, when a client adds a new task to the list, all other clients receive the update and the list is re-rendered to reflect the changes. The above example, however, doesn't demonstrate how to remove or update tasks in the list. This is covered in the next section.
382
+
383
+
384
+
[[list-signal-api]]
385
+
==== ListSignal API
386
+
387
+
The client-side API of the [classname]`ListSignal` provides methods to insert and remove items. The [classname]`ListSignal` is a sequence of [classname]`ValueSignal` entries. Therefore, its API is about how the entries are added to the list or removed, and how the concurrent operations regarding the structure of the entries is handled.
388
+
389
+
As this is currently under active development, more methods and functionalities are added with each new release. The currently available ones are [methodname]`inserLast` and [methodname]`remove`. These are described below:
390
+
391
+
`insertLast(value: T): Operation`:: Inserts a new value at the end of the list. The returned `Operation` object can be used to chain further operations via the `result` property, which is a `Promise`. The chained operations are resolved after the current operation is completed and confirmed by the server.
392
+
`remove(item: ValueSignal<T>): Operation`:: Removes the given item from the list. The returned `Operation` object can be used to chain further operations via the `result` property, which is a `Promise`. The chained operations are resolved after the current operation is completed and confirmed by the server.
393
+
394
+
The following example demonstrates how to create a to-do list view that enables concurrent users to add, remove, and update tasks in a shared list, with no changes needed on the server-side:
395
+
396
+
[source,tsx]
397
+
.todo.tsx
398
+
----
399
+
import { TodoService } from "Frontend/generated/endpoints.js";
400
+
import {
401
+
Button,
402
+
Checkbox,
403
+
Icon,
404
+
TextField,
405
+
TextArea,
406
+
HorizontalLayout,
407
+
VerticalLayout
408
+
} from "@vaadin/react-components";
409
+
import { effect, useSignal, type ValueSignal} from "@vaadin/hilla-react-signals";
As demonstrated in the above example, each entry in the [classname]`ListSignal<T>` is a [classname]`ValueSignal<T>` itself. Each value can be updated individually using the available API of the `ValueSignal`. The changes to each individual entry are automatically propagated to all other clients that are subscribed to each entry of the [classname]`ListSignal`. This enables the React rendering process to render only the updated entry, instead of re-rendering the whole list.
499
+
500
+
294
501
[[method-parameters]]
295
502
== Service Method Parameters
296
503
@@ -340,7 +547,7 @@ public class VoteService {
340
547
The above example demonstrates a simple voting service that returns different [classname]`NumberSignal` instances based on the name of the voting option. The client-side code can first ask for the available options, and then subscribe to each individual signal instance to send updates and to receive real-time updates when voting happens.
341
548
342
549
[IMPORTANT]
343
-
It's vitally important to make sure that the behaviour of the service method returning a signal instance should be deterministic, meaning that the same input parameters should always produce the same output. This is necessary to ensure that the state is consistently shared across all of the clients.
550
+
It's vitally important to make sure that the behaviour of the service method returning a signal instance is deterministic. The same input parameters should always produce the same output. This is necessary to ensure that the state is consistently shared across all of the clients.
344
551
345
552
346
553
[[security]]
@@ -351,9 +558,11 @@ Security with full-Stack signals has a few nuances of which you should be aware.
351
558
352
559
=== Controlling Browser-Callable Service Access
353
560
354
-
Full-stack signals are exposed by the services that are annotated with [classname]`@BrowserCallable` -- or the synonym, [classname]`@Endpoint`. This means the services that expose the signals are secured by the same security rules as any other service using the [classname]`@AnonymousAllowed`, [classname]`@PermitAll`, [classname]`@RolesAllowed`, or [classname]`@DenyAll` on the class or the individual methods. For more information on how to secure the services, see the <<./security/intro#, security documentation>>.
561
+
Full-stack signals are exposed by the services that are annotated with [classname]`@BrowserCallable` -- or the synonym, [classname]`@Endpoint`. This means the services that expose the signals are secured by the same security rules as any other service using the [classname]`@AnonymousAllowed`, [classname]`@PermitAll`, [classname]`@RolesAllowed`, or [classname]`@DenyAll` on the class or the individual methods.
562
+
563
+
For more information on how to secure the services, see the <<./security/intro#, security documentation>>.
355
564
356
565
357
566
=== Fine-Grained Signal Access Control
358
567
359
-
Browser-callable access control can be considered as basic security for signals, since it only allows limited control over the access to the signals. However, there are situations that require finer control over the signals. For example, you might want to allow anyone to subscribe to a signal, but only certain logged-in users with a specific role that allows them to update the value of that signal. This level of control is not yet implemented, but it's expected to be added in future releases.
568
+
Browser-callable access control can be considered as basic security for signals, since it only allows limited control over the access to the signals. However, there are situations that require finer control over the signals. For example, you might want to allow anyone to subscribe to a signal, but only certain logged-in users with a specific role to update the value of that signal. This level of control is not yet implemented, but it's expected to be added in future releases.
0 commit comments