contextRegistry
Register context instances and resolve them by their runtime class.
Register context instances and resolve them by their runtime class.
Overview
ContextRegistry is a utility class for explicit context sharing across component trees.
It stores context instances by constructor and lets you resolve them by type. Lookup uses instanceof, so querying by a base class can return a subclass instance.
Try it live
The registry stores an instance. Editing the draft alone does not replace it.
import { ContextRegistry, html, ref } from 'regor'
class Workspace {
constructor(readonly name: string) {}
}
export function createRegistry(interactive = false) {
const registry = new ContextRegistry()
const name = ref('Regor Studio'),
resolved = ref('Nothing registered yet.')
const register = () => {
registry.register(new Workspace(name()))
resolved(registry.require(Workspace).name)
}
const clear = () => {
registry.unregisterByClass(Workspace)
resolved(registry.find(Workspace)?.name ?? 'Nothing registered yet.')
}
return { interactive, name, resolved, register, clear }
}
export const registryTemplate = html` <div class="guide-demo guide-demo--split">
<div class="guide-controls">
<label for="registry-name">Workspace instance</label
><input
id="registry-name"
type="text"
maxlength="40"
r-model="name"
:value="name"
:disabled="!interactive"
/>
<div class="guide-session-bar">
<button type="button" @click="register" :disabled="!interactive">
Register / replace</button
><button type="button" @click="clear" :disabled="!interactive">
Unregister
</button>
</div>
</div>
<div class="guide-readout">
<span class="guide-kicker">RESOLVED BY CLASS</span
><output class="directive-output" role="status">{{ resolved }}</output>
<p>
The registry stores an instance. Editing the draft alone does not replace
it.
</p>
</div>
</div>` import { createApp, RegorConfig, useScope } from 'regor'
import { apiExamples } from './examples'
const element = document.getElementById('api-demo')
const name = element?.dataset.example
if (element && name && Object.hasOwn(apiExamples, name)) {
const example = apiExamples[name as keyof typeof apiExamples]
const scope = useScope<object>(() => example.create(true))
const config =
'config' in scope.context && scope.context.config instanceof RegorConfig
? scope.context.config
: undefined
createApp(scope, { element, template: example.template }, config)
} Register a workspace, replace it, or unregister it. Lookup uses the class rather than the draft input.
Usage
import { ContextRegistry } from 'regor'
class AppServices {
readonly apiBase = '/v1'
}
const registry = new ContextRegistry()
registry.register(new AppServices())
const services = registry.require(AppServices) API
register(context)
Registers an instance under its runtime constructor.
- If another instance of the same constructor is already registered, it is replaced.
unregisterByClass(ContextClass)
Removes the entry for a constructor.
- No-op when that constructor is not registered.
unregister(context)
Removes an instance only if it is currently the registered value for its constructor.
- This prevents deleting a newer replacement instance registered later.
find(ContextClass)
Returns the first registered instance matching instanceof ContextClass, or undefined.
require(ContextClass)
Returns the same result as find, but throws when not found.
- Error message format:
ClassName is not registered in ContextRegistry.
Example: parent provides registry, child resolves from head.requireContext
import { ComponentHead, ContextRegistry, defineComponent } from 'regor'
class AppServices {
readonly apiBase = '/v1'
}
class Parent {
readonly registry = new ContextRegistry()
constructor() {
this.registry.register(new AppServices())
}
}
class Child {
readonly apiBase: string
constructor(head: ComponentHead<object>) {
// Parent can provide registry from any component context shape.
const parent = head.requireContext(Parent)
const services = parent.registry.require(AppServices)
this.apiBase = services.apiBase
}
}
const parent = defineComponent<Parent>('<div><slot></slot></div>', {
context: () => new Parent(),
})
const child = defineComponent<Child>('<p r-text="apiBase"></p>', {
context: (head) => {
return new Child(head)
},
}) Example: component ctx <=> component ctx communication through parent registry
import {
ComponentHead,
ContextRegistry,
createApp,
defineComponent,
html,
ref,
} from 'regor'
class Parent {
components = { producer, consumer }
registry = new ContextRegistry()
}
class Consumer {
message = ref('idle')
private readonly parent: Parent
constructor(head: ComponentHead<object>) {
this.parent = head.requireContext(Parent)
this.parent.registry.register(this)
}
receive = (next: string): void => {
this.message(next)
}
}
class Producer {
private readonly parent: Parent
constructor(head: ComponentHead<object>) {
this.parent = head.requireContext(Parent)
this.parent.registry.register(this)
}
send = (): void => {
// direct component-ctx -> component-ctx call through registry
const consumer = this.parent.registry.require(Consumer)
consumer.receive('message-from-producer')
}
}
const producer = defineComponent<Producer>(
html`<button class="send" @click="send">send</button>`,
{
context: (head) => new Producer(head),
},
)
const consumer = defineComponent<Consumer>(
html`<p class="value">{{ message }}</p>`,
{
context: (head) => new Consumer(head),
},
)
const parent = defineComponent<Parent>(
html`<section>
<Producer></Producer>
<Consumer></Consumer>
</section>`,
{
context: () => new Parent(),
},
)
createApp(
{ components: { parent } },
{
element: document.querySelector('#app')!,
template: '<Parent></Parent>',
},
) Registering another instance of the same constructor replaces the earlier entry. find can check for absence without throwing.