Skip to content

createServiceMixin

ts
createServiceMixin<Instance, Target, Options extends object = object>(
  definition: ServiceMixinDefinition<Target, Options>,
): ServiceMixin<Instance, Target, Options>

Builds the mixin-and-decorator pair over a service. Every built-in with* is one of these.

The definition

ts
interface ServiceMixinDefinition<Target, Options> {
  hook: string;
  target: (instance: Base) => Target;
  defaultImmediate?: boolean;
  use: (target: Target, options: Options) => Service<unknown, unknown>;
  handleResult?: (instance: Base, result: unknown) => void;
}
FieldDoes
hookthe one method name the service owns
targetthe default target resolver
defaultImmediatewhether the first delivery is asked for unless the caller says otherwise
usecalls the service factory
handleResultdoes something with what the hook returned — withRaf schedules a render

Usage

ts
interface FocusHook {
  
focused
?(
props
: FocusProps): void;
} export const
withFocus
=
createServiceMixin
<FocusHook, Element>({
hook
: 'focused',
target
: (
instance
) =>
instance
.
$el
,
use
: (
target
) =>
useFocus
(
target
),
});
js
class Field extends withFocus(Base) {
  focused({ hasFocus }) {}
}

use receives the service's options only

target, manual and immediate are removed before use is called and they are absent from its Options type — which is why use: (target, options) => useDrag(target, options) is correct and needs no filtering.

Override $mount(), not mounted()

createServiceMixin() overrides $mount() and $unmount(), which is what lets a component write its own mounted() without super.mounted() and still subscribe.

The rule is about what a mixin overrides, not who wrote it. A userland mixin that puts its work in mounted() needs its subclasses to call super.mounted(). The way not to need that is to override $mount() — or to use this function, which already does.

What it gives the class

  • the hook, bound for each mount cycle;
  • $services.<hook> as a Toggle, typed through ServiceHandles<'<hook>'>;
  • the two call forms, mixin and class decorator;
  • the service.missing-target check on a caller-supplied resolver.

One hook per class

A mixin names one hook and binds one subscription per cycle. That is its limit, and a component with a dynamic set of subscriptions calls subscribe() itself. See Mixins.

MIT Licensed