Skip to content

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:

ts
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:

MethodReturns
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) with Class is not Injectable and not Optional.
  • Requires an active event context. useControllerContext() only works inside handlers and interceptors; calling it at module top level (or outside an event) throws No active event context.

Released under the MIT License.