Skip to main content

Computed

A Computed is an extension of the State Class that computes its value from a specified function. Computed States are a powerful concept, that lets us build dynamic data depending on other data. To avoid unnecessary recomputations, the Computed Class caches the computed value and recomputes it only when an actual dependency has changed. All you need to do to instantiate a Computed State, is to call createComputed() and specify a compute function which computes the value for the Computed Class.

const MY_COMPUTED = createComputed(() => {
return `My name is '${MY_NAME.value}' and I am ${MY_AGE.value} years old.`;
});

A Computed magically tracks used dependencies (such as States or Collections) and automatically recomputes when one of its dependencies updates. In the above code snippet, it would, for example, recompute when the current value of MY_NAME changes from 'jeff' to 'hans'.

MY_COMPUTED.value; // Returns "My name is 'jeff' and I am 10 years old"
MY_NAME.set('hans');
MY_COMPUTED.value; // Returns "My name is 'hans' and I am 10 years old"

However, in some cases the automatic detection of dependencies doesn't work correctly.

const MY_COMPUTED = createComputed(async () => {
const age = await getAge(MY_NAME.value);
return `My name is '${MY_NAME.value}' and I am ${age} years old.`;
}); // ❌ Doesn't recompute when 'MY_NAME' updates

For example, that is the case when the compute method is async.

MY_COMPUTED.value; // Returns "My name is 'jeff' and I am 10 years old"
MY_NAME.set('hans');
MY_COMPUTED.value; // ❌ Returns "My name is 'jeff' and I am 10 years old"

In order to solve this problem we need to manually tell the Computed Class which dependencies it depends on. We can give these hard-coded dependencies to the Computed Class as a second argument.

const MY_COMPUTED = createComputed(async () => {
const age = await getAge(MY_NAME.value);
return `My name is '${MY_NAME.value}' and I am ${age} years old.`;
}, [MY_NAME]); // ✅ Does recompute when 'MY_NAME' updates

MY_COMPUTED.value; // Returns "My name is 'jeff' and I am 10 years old"
MY_NAME.set('hans');
MY_COMPUTED.value; // ✅ Returns "My name is 'hans' and I am 10 years old"

🔨 Use case

A Computed State is useful whenever we need a State that is computed depending on other States.

const IS_AUTHENTICATED = createComputed(() => {
return TOKEN.exists && USER_ID.exists && EXPIRATION_TIME.value > 0;
});

This is the case for our IS_AUTHENTICATED State, which depends on several other States determining whether the current user is authenticated or not. These include the TOKEN, CURRENT_USER and EXPIRATION_TIME. If, for instance, the USER_ID value changes, the Computed Class will recompute the IS_AUTHENTICATED state.

IS_AUTHENTICATE.value; // Returns 'true'
USER_ID.set(undefined);
IS_AUTHENTICATE.value; // Returns 'false'

⛳️ Sandbox

Test the Computed Class yourself. It's only one click away. Just select your preferred Framework below.

  • React
  • Vue (no example yet :/)

📭 Props

new Computed(agileInstance, config);
// or
createComputed(computedFunction, deps);
// or
createComputed(computedFunction, config);

computedFunction

Method used to compute the value of the Computed Class.

const MY_COMPUTED = createComputed(() => 1 + 1);
MY_COMPUTED.value; // Returns '2'

This function will be called on each dependency mutation. Dependencies can for example be States or Collections. In the above code snippet MY_COMPUTED is independent, but in the blow example it depends on the CURRENT_USER_ID State and USERS Collection.

const MY_COMPUTED = createComputed(() => {
const user = USERS.getItemValue(CURRENT_USER_ID.value);
return `My name is '${user?.name} and I am ${user?.age} years old.`;
});
MY_COMPUTED.value; // Returns "My name is 'hans' and I am 10 years old."
USERS.update(CURRENT_USER_ID.value, {name: 'jeff'})
MY_COMPUTED.value; // Returns "My name is 'jeff' and I am 10 years old."

deps

In order not to rely 100% on the automatic detection of dependencies, we can pass an optional array of hard coded dependencies into the Computed Class.

const MY_COMPUTED = createComputed(() => {
return `My name is '${MY_NAME.value}' and I am ${MY_AGE.value} years old.`;
}, [MY_NAME, MY_AGE]);
MY_COMPUTED.value; // Returns "My name is 'hans' and I am 10 years old."
MY_NAME.set('jeff');
MY_COMPUTED.value; // Returns "My name is 'jeff' and I am 10 years old."

In most cases, it isn't necessary to provide any hard-coded dependency. However, it might occur that the Computed Class fails to autodetect a particular dependency. You can check if all dependencies got correctly noticed by giving each used Agile Sub Instance a unique key and reviewing the deps array.

MY_COMPUTED.deps; // Returns '[Observer('myName'), Observer('myAge')]'

config

Beside the computed function and deps array a Computed takes an optional configuration object.

createComputed(() => {}, {
key: "myKey",
dependents: [MY_STATE_2]
});

Here is a Typescript Interface for quick reference. However, each property is explained in more detail below.

export interface StateConfigInterface {
key?: StateKey;
dependents?: Array<Observer>;
isPlaceholder?: boolean;
}

key

The optional property key/name should be a unique string/number to identify the Computed later.

createComputed(() => {}, {
key: "myKey"
});

We recommend giving each Computed a unique key since it has only advantages:

  • helps us during debug sessions
  • makes it easier to identify the Computed
  • no need for separate persist Key
TypeDefaultRequired
string \| numberundefinedNo

dependents

🔥warning

This property is mainly thought for the internal use.

Specifies which States depend on this Computed Class.

createComputed(() => {}, {
dependents: [MY_STATE_2]
});

So if this Computed Class mutes and is ingested into the runtime, the depending States are ingested into the runtime too.

TypeDefaultRequired
Array<Observer>[]No

isPlaceholder

🔥warning

This property is mainly thought for the internal use.

Defines whether the Computed is a placeholder.

const MY_COMPUTED = createComputed(() => {}, {
isPlaceholder: true
});

MY_COMPUTED.exists(); // false

Computed Classes are, for example, placeholder when AgileTs needs to hold a reference to them, even though they aren't instantiated yet.

TypeDefaultRequired
booleanfalseNo

🟦 Typescript

The Computed Class is almost 100% typesafe and takes an optional generic type for type safety of its value.

const MY_COMPUTED = createComputed<string>(() => {
return 'Now Compute Function has to return String!';
});