Resources and Registries ​
Overview ​
Owl provides two ordered, reactive collection types:
- Resource: an ordered set of items (no keys). Useful for collections where multiple parts of the application contribute items — for example, systray entries, error handlers, or keyboard shortcuts.
- Registry: an ordered key-value map. Useful when items need to be looked up by name — for example, a component registry or action registry.
Both support sequencing (to control ordering) and optional type validation. Both expose their contents as reactive computed values, so reading them in a component or effect automatically subscribes to changes.
Resource ​
Creating a Resource ​
A Resource holds an ordered set of items. Use add() and delete() to manage items, and items() to read them:
const commands = new Resource();
commands.add({ label: "Save", action: save });
commands.add({ label: "Undo", action: undo });
commands.items(); // [{ label: "Save", ... }, { label: "Undo", ... }]
commands.has(someItem); // true or false (reference equality)
commands.delete(someItem);Methods are chainable:
commands.add(item1).add(item2).add(item3);A name option can be provided for better error messages:
const commands = new Resource({ name: "commands" });Sequencing ​
Items are sorted by a numeric sequence value (default: 50, ascending). Lower numbers appear first:
const r = new Resource();
r.add("first", { sequence: 10 });
r.add("middle"); // sequence 50 (default)
r.add("last", { sequence: 100 });
r.items(); // ["first", "middle", "last"]useResource ​
In components and plugins, useResource() adds items to a resource and automatically removes them when the component or plugin is destroyed:
class SystrayPlugin extends Plugin {
items = new Resource({ name: "systray" });
}
class ClockComponent extends Component {
systray = plugin(SystrayPlugin);
setup() {
useResource(this.systray.items, [{ label: "Clock", render: () => this.renderClock() }]);
// items are removed when ClockComponent is destroyed
}
}Type Validation ​
Pass a validation option to validate items on add():
const commands = new Resource({
name: "commands",
validation: t.object({ label: t.string(), action: t.function() }),
});
commands.add({ label: "Save", action: save }); // ok
commands.add({ label: 123 }); // throws validation errorRegistry ​
Creating a Registry ​
A Registry holds an ordered key-value map. Use add(), get(), delete(), and has() to manage entries:
const views = new Registry({ name: "views" });
views.add("list", ListComponent);
views.add("form", FormComponent);
views.get("list"); // ListComponent
views.has("form"); // true
views.delete("form");get() throws an error if the key is not found. Pass a default value to avoid the error:
views.get("kanban"); // throws KeyNotFoundError
views.get("kanban", null); // returns nullentries() returns [key, value] tuples, items() returns values only:
views.entries(); // [["list", ListComponent], ["form", FormComponent]]
views.items(); // [ListComponent, FormComponent]Methods are chainable:
views.add("list", ListComponent).add("form", FormComponent);addById ​
For objects that have an id property, addById() is a shorthand that uses item.id as the key:
const actions = new Registry({ name: "actions" });
const action = { id: "save", label: "Save", run: () => {} };
actions.addById(action);
actions.get("save"); // returns the action objectRegistry Sequencing ​
Like resources, registry entries are sorted by sequence (default: 50, ascending):
const r = new Registry();
r.add("a", "first", { sequence: 10 });
r.add("b", "middle"); // sequence 50
r.add("c", "last", { sequence: 100 });
r.items(); // ["first", "middle", "last"]
r.entries(); // [["a", "first"], ["b", "middle"], ["c", "last"]]Registry Type Validation ​
Pass a validation option to validate values on add() and addById():
const views = new Registry({
name: "views",
validation: t.constructor(Component),
});
views.add("list", ListComponent); // ok
views.add("oops", "not a component"); // throws validation errorReactivity ​
Both Resource.items and Registry.items/Registry.entries are reactive computed values. Reading them inside a component render or an effect automatically subscribes to changes:
const registry = new Registry();
registry.add("a", 1);
effect(() => {
console.log(registry.items()); // re-runs when entries change
});
registry.add("b", 2);
await Promise.resolve();
// effect re-runs, logs [1, 2]In component templates, this works naturally:
class CommandPalette extends Component {
static template = xml`
<ul>
<li t-foreach="this.commands.items()" t-as="cmd" t-key="cmd.label">
<t t-out="cmd.label"/>
</li>
</ul>`;
commands = plugin(CommandPlugin);
}The component will re-render whenever items are added or removed from the resource.