@Observe
The @Observe
annotation is used to define an observe method for an
observer. Observers are typically used for querying arez state and reflecting
that state as side-effects. As such the observe method defaults to being run in a read-only
transaction but this can be modified by setting the
@Observe.mutation
parameter to true
which will change the
transaction mode to read-write.
An example where an @Observe
method can be used is to automatically update
a view when data changes. For example, imagine you wanted to display the value of 1 Bitcoin in
Australian dollars as the value changes over time. Assuming the currency is observable, the
@Observe
method may look something like:
@ArezComponent
public abstract class CurrencyView
{
@CascadeDispose
final Currency bitcoin = new Arez_Currency();
@Observe
void renderView()
{
final Element element = WindowGlobal.document().getElementById( "currencyTracker" );
assert null != element;
element.innerHTML = "1 BTC = $" + bitcoin.getAmount() + "AUD";
}
}
This is enough to create a basic web application just using the Arez @Observe
primitive.
Decoupling the execution and scheduling of the Observer
Sometimes it is useful to decouple the execution of the @Observe
method and the
rescheduling of the method when a dependency change is detected. In Arez, this is possible when you pair
the @Observe
method with an @OnDepsChange
method. The
@OnDepsChange
is invoked by the Arez runtime when it detects a change has occurred.
It is up to application to invoke Observer.schedule()
to schedule an
update.
One scenario where this is commonly used is to limit the rate at which an observer reacts. For example there may not be a need to update the website every time the currency conversion rate changes if it is changing multiple times a second. It may be sufficient to update the value once every 2 seconds and this could be achieved with code such as:
@ArezComponent
public abstract class CurrencyView
{
@CascadeDispose
final Currency bitcoin = new Arez_Currency();
@Observe
void render()
{
final Element element = WindowGlobal.document().getElementById( "currencyTracker" );
assert null != element;
element.innerHTML = "1 BTC = $" + bitcoin.getAmount() + "AUD";
}
@OnDepsChange
void onRenderDepsChange( @Nonnull final Observer observer )
{
debounce( observer::schedule, 2000 );
}
private void debounce( @Nonnull final SafeProcedure action, final long timeInMillis )
{
// Execute this action at most one every timeInMillis
...
}
}
Allowing the application to execute observe methods
The observers documentation describes "tracker" observers as those where the application is responsible for executing the observe method. This is useful when you need to integrate with other frameworks that already contain their own scheduler.
This is modelled with a pair of methods; one annotated with @Observe
and one
annotated with @OnDepsChange
. In addition it is necessary that the
@Observe.executor
parameter on the @Observe
annotation is set to Executor.APPLICATION
.
This approach is used in libraries such as React4j that integrate Arez into external schedulers. An example that demonstrates something similar is:
@ArezComponent
public abstract class CurrencyView
{
// A read-only observer that renders
@Observe( executor = Executor.EXTERNAL )
public ReactNode render()
{
//Render component here
...
}
void onRenderDepsChange()
{
// Schedule this component
scheduleRender();
}
...
}