Control Injections and Read Status
This page shows how to start, stop, and remove an injection, and how to read its status inside and outside components. Every top-level injection has a command handle (start / stop / remove) and a status handle (status and lastError); for the concepts, see Core Concepts.
Control from Outside a Component
Get the handles from the Core instance by injection name:
const panel = core.injection('panel');
await panel.command.stop(); // unmount the component, keep the registration
panel.command.start(); // find the mount target again and mount
console.log(panel.statusHandle.getSnapshot());Handles can be obtained in any module that can access core; they don't need to be passed from where apply() is called. If the name doesn't exist, MAKOO_INJECTION_NOT_FOUND is thrown.
Example: Toggle a Panel from the Script Manager Menu
import { gmMenu } from '@makoojs/cli/monkey';
gmMenu.register('Show / hide panel', () => {
const panel = core.injection('panel');
const status = panel.statusHandle.getSnapshot();
if (status === 'idle') panel.command.start();
else void panel.command.stop();
});Control Itself from Inside a Component
A component can get the command handle of its owning injection, for example to make a close button:
<script setup lang="ts">
import { useMakooComponent } from '@makoojs/vue';
const makoo = useMakooComponent();
</script>
<template>
<button @click="makoo.stop()">Close</button>
</template>import { useComponentCommand } from '@makoojs/react';
export default function Panel() {
const { stop } = useComponentCommand();
return <button onClick={() => stop()}>Close</button>;
}Once a component stops itself it is unmounted and can't start itself again. To reopen it, code outside the component (such as the menu above) calls start().
The difference between stop() and remove():
| Operation | Afterwards |
|---|---|
stop() | Registration kept; can start() again |
remove() | Unregistered, name released; the old handles are permanently invalid |
Show Status Inside a Component
<script setup lang="ts">
import { useMakooComponent } from '@makoojs/vue';
const makoo = useMakooComponent();
const status = makoo.status;
const button = makoo.attachedListener('site-button');
</script>
<template>
<p>Component: {{ status }}, site button: {{ button }}</p>
</template>import { useAttachedListenerStatus, useComponentStatus } from '@makoojs/react';
export default function Panel() {
const status = useComponentStatus();
const button = useAttachedListenerStatus('site-button');
return <p>Component: {status}, site button: {button}</p>;
}Vue returns read-only refs, which are unwrapped automatically in the template once assigned to top-level variables in <script setup>; React hooks return status strings. Subscriptions are released automatically when the component unmounts.
When a React component only needs part of the information, passing a selector reduces renders:
const ready = useAttachedListenerStatus('site-button', (status) => status === 'bound');Subscribe to Status Outside a Component
const status = core.injection('panel').statusHandle;
const unsubscribe = status.subscribe(() => {
if (status.getSnapshot() === 'failed') console.warn('panel failed:', status.lastError);
});The subscribe() callback receives no arguments; call getSnapshot() inside the callback. When you no longer need it, call the returned unsubscribe().
A component's status handle can also read the status of its attached listeners. Because core.injection() also returns Each Injections, narrow by kind first:
const panel = core.injection('panel');
if (panel.kind === 'component') {
for (const name of panel.statusHandle.attachedListenerNames) {
console.log(name, panel.statusHandle.attachedListener(name).getSnapshot());
}
}Control Each Child Injection
An Each Injection declared with injectEach() is controlled as a whole through its own handles, and lists its Child Injections in children:
const badges = core.injection('badges');
if (badges.kind === 'component-each') {
console.log(badges.statusHandle.getSnapshot()); // 'collecting'
for (const child of badges.children) {
console.log(child.command.name, child.statusHandle.getSnapshot()); // 'badges#1' 'mounted'
}
await badges.children[0]?.command.stop(); // unmount only the first badge
}childrenis a snapshot in document order. Read it again after the page changes; there is no subscription for Children being added or removed.- A stopped or ended Child leaves
children, and its element doesn't get a component again until the whole Each Injection is stopped and started. A Child command has onlystop—Children run once and are not registered under a name, so there is nothing to restart or unregister. badges.command.stop()unmounts every Child and stops watching for new matches;start()mounts on every match again.- A failing Child doesn't change the Each Injection's status; check the Child's own
statusHandle.lastError.
Wait for a Stop to Finish
stop() and remove() return Promises that resolve after cleanup finishes. When you need to confirm resources have been released before continuing, await them:
try {
await core.injection('panel').command.remove();
} catch (error) {
// Cleanup failed: error is a MakooAggregateError and the injection has been closed
console.error(error);
}When you only send a stop request and don't care about the result, void core.injection('panel').command.stop() is enough; cleanup failures are still printed to the console.
Switch to a Different Configuration
A declaration is fixed at apply() time. To change configuration such as the selector, timeout, or props object, remove it first and declare it again:
await core.injection('panel').command.remove();
core.apply([
inject({ name: 'panel', injectAt: '#new-place', adapter: 'vue', component: Panel })
]);Old handles obtained before removal still point to the old injection; call core.injection('panel') again to get the new handle.
Destroy the Whole Instance
core.dispose() removes all injections in the instance, and afterwards the instance no longer accepts new declarations:
await core.dispose();When the page reloads or navigates away, the browser cleans up along with the instance, so you usually don't need to call it manually. It is only needed for custom hot update boundaries; see Hot Updates.