Functional Instantiation
Most dependencies are resolved via constructor injection. But sometimes you need to create an instance on demand at runtime — inside an interceptor, conditionally based on request data, or for a class that isn't part of the controller's constructor.
useControllerContext().instantiate
The useControllerContext() composable provides an instantiate function that creates class instances through Moost's DI system, respecting scopes, provide registries, and replace registries:
import { Injectable, defineBeforeInterceptor, defineAfterInterceptor, useControllerContext } from 'moost'
@Injectable()
class MetricsCollector {
startTimer() { /* ... */ }
record() { /* ... */ }
}
const startMetrics = defineBeforeInterceptor(async (reply) => {
const { instantiate } = useControllerContext()
const metrics = await instantiate(MetricsCollector)
metrics.startTimer()
})
const recordMetrics = defineAfterInterceptor(async (response, reply) => {
const { instantiate } = useControllerContext()
const metrics = await instantiate(MetricsCollector)
metrics.record()
})instantiate(ClassName) returns a Promise<T> — it resolves the class through the same DI container used for constructor injection, scoped to the current controller instance.
What useControllerContext Returns
Beyond instantiate, the composable exposes the current controller's runtime context:
| Method | Returns |
|---|---|
instantiate(Class) | DI-resolved instance of the given class |
getController() | Current controller instance |
getMethod() | Current handler method name |
getRoute() | Full route path (prefix + handler path) |
getPrefix() | Controller's computed prefix (accumulated from all parents) |
getControllerMeta() | Class-level metadata |
getMethodMeta(name?) | Method-level metadata |
getPropMeta(name) | Property-level metadata (alias for getMethodMeta(name)) |
getScope() | Controller's injectable scope — true | 'SINGLETON' | 'FOR_EVENT' (plain @Controller()/@Injectable() classes yield true, which also means singleton — check scope === 'FOR_EVENT', not scope === 'SINGLETON') |
getParamsMeta() | Array of handler parameter metadata (always an array; empty when none) |
getPropertiesList() | Decorated property keys — (string | symbol)[] |
When to Use
- Interceptors that need a service not in the controller's constructor
- Conditional creation — instantiate a class only when a feature flag or request condition is met
- Dynamic dispatch — choose which class to instantiate based on runtime data
For most cases, constructor injection is simpler and more predictable. Use instantiate when static injection patterns don't fit.
Gotchas
- The target class must be DI-visible.
instantiate()rejects classes without@Injectable()(or a matching provide-registry entry) withClass is not Injectable and not Optional. - Adapter classes resolve to the attached adapter.
instantiate(MoostHttp)(orMoostCli,MoostWf,MoostWs) returns the adapter instance your app attached withapp.adapter()— also when you attached a subclass, and also when the class you pass comes from a second copy of the adapter package (a library loaded on the other side of a bundler/SSR externalization split). The second-copy case logs a one-time warning: fix the duplicate install, since other module state is split too. Versions <= 0.6.42 reject both cases withClass is not Injectable. - Requires an active event context.
useControllerContext()only works inside handlers and interceptors; calling it at module top level (or outside an event) throwsNo active event context.