Attribute Mixins: a declarative-functional alternative to Directives

We come from streams-oriented programming. Nothing new, just functional/reactive programming rebranded to highlight that in a world flooded by imperative programming anti-patterns, going functional with reactive streams is a big deal. Now it's time to rediscuss Attribute Mixins: a declarative pattern introduced by Rimmel.js to extend any component with custom functionality. An Attribute Mixin is a simple function that returns an "Attribuend" Object (a fancy name for an object of attributes you want to set on an HTML element). const MakeItRed = () => ({ class: 'primary', style: { color: 'red' } }); this is a simple mixin that sets the class and style of any element you merge it into. Using it in a template is equally simple: const template = rml` click me `; // makes a Rimmel understands the above pattern and will cleverly merge anything returned by the mixin. Event Listeners Setting classes and styles is fine, pretty simple, but in fact anything that can be merged with JavaScript can also be merged this way with a mixin, event handlers included: const Clickable = () => ({ onclick: e => console.log('clicked'), }); The above will set a click listener on the target element. Mixing in a reactive stream Ok, now is where things can get exciting. A reactive stream takes events in and spits results out. Guess this can be used for something like Drag'n'drop: you take mousedown and mousemove events in, then emit [x,y] coordinates as a CSS translation. First, the code (an extract): const Draggable = () => { const toCSSTransform = () => map(([Δx, Δy]) => ({ transform: `translate(${Δx}px, ${Δy}px)` })); const toClientXY = (e: PointerEvent) => [e.clientX -eX, e.clientY -eY] ; const dnd = new Subject().pipe( switchMap((e: PointerEvent) => { e.preventDefault(); const {x0, y0} = currentXY(e.currentTarget.style.transform); const [eX, eY] = [ e.clientX -parseFloat(x0), e.clientY -parseFloat(y0) ]; return mousemove.pipe( map(toClientXY), toCSSTransform(), takeUntil(mouseup), ); }), ); return { class: 'draggable', onmousedown: dnd, style: dnd, }; }; Run on Stackblitz The function above has the same shape: a simple function that returns an attribuend object. It sets a class, then onmousedown, which will make it receive mousedown events and emits style, that will be a sink through which it will be able to emit updates back to the underlying element. dnd is the reactive stream: an RxJS Subject that does a bit of coordinate calculations on any subsequent mouse movements until the button is released. With Rimmel any DOM event can be bound directly to an observable stream, which makes it a zero-boilerplate joy to work with. No need to mess with addEventListener/removeEventListener, as it's all implicitly handled behind the scenes. If you've not familiar with the way Drag'n'drop is implemented with RxJS, there are plenty of articles explaining how to do it and how it works, so I'd rather skip that part here. Declarative = no access to the underlaying element What is truly innovative here, compared to all other implementations is that mixins don't get a reference to the underlying element. This is not a limitation but a superpower, actually. It enables you to think in purely declarative terms, which makes this pattern greatly suited for testing, loose coupling and a strong separation of concerns. You can use one mixin just for drag and drop and other mixins for otcreate a Draggable mixin thather functionality that you can keep neatly isolated and well organised. Sync vs Async Ok, now the coolest part. The examples above all set the specified properties/event listeners when when the component is mounted, synchronously. It doesn't have to be like that. You can go async, as well. Both the mixin and every property of its attribuend object can be async objects (Promises or Observables!), so you can activate functionaliy based on a delay, or your custom activation logic and signals. Suppose we want to make an element draggable on demand, depending on the state of a checkbox or any toggle element. What you can create is an async mixin that takes a stream as a parameter and will dynamically change its behavior based on its latest emission (state) const Draggable = async (trigger) => { // If an activation trigger was specified, just wait for it before activating trigger && await firstValueFrom(trigger); // so the Attribuend object is not emitted immediately. const dnd = new Subject().pipe( // the same drag'n'drop logic... ); return { onmousedown: dnd, style: dnd, }; }; The above enables us to just connect a button to this mixin, so it can be made draggable after clicking it: const trigger = new Subject(); const template = rml` Make it draggable

Apr 18, 2025 - 07:12
 0
Attribute Mixins: a declarative-functional alternative to Directives

We come from streams-oriented programming. Nothing new, just functional/reactive programming rebranded to highlight that in a world flooded by imperative programming anti-patterns, going functional with reactive streams is a big deal.

Now it's time to rediscuss Attribute Mixins: a declarative pattern introduced by Rimmel.js to extend any component with custom functionality.

An Attribute Mixin is a simple function that returns an "Attribuend" Object (a fancy name for an object of attributes you want to set on an HTML element).

const MakeItRed = () => ({
  class: 'primary',
  style: { color: 'red' }
});

this is a simple mixin that sets the class and style of any element you merge it into. Using it in a template is equally simple:

const template = rml`
  
`;

// makes a 

Rimmel understands the above pattern and will cleverly merge anything returned by the mixin.

Event Listeners

Image description

Setting classes and styles is fine, pretty simple, but in fact anything that can be merged with JavaScript can also be merged this way with a mixin, event handlers included:

const Clickable = () => ({
  onclick: e => console.log('clicked'),
});

The above will set a click listener on the target element.

Mixing in a reactive stream

Ok, now is where things can get exciting. A reactive stream takes events in and spits results out. Guess this can be used for something like Drag'n'drop: you take mousedown and mousemove events in, then emit [x,y] coordinates as a CSS translation.

First, the code (an extract):

const Draggable = () => {
  const toCSSTransform = () => map(([Δx, Δy]) => ({
    transform: `translate(${Δx}px, ${Δy}px)`
  }));

  const toClientXY = (e: PointerEvent) =>
    [e.clientX -eX, e.clientY -eY]
  ;

  const dnd = new Subject().pipe(
    switchMap((e: PointerEvent) => {
      e.preventDefault();

      const {x0, y0} =
        currentXY(e.currentTarget.style.transform);

      const [eX, eY] = [
        e.clientX -parseFloat(x0),
        e.clientY -parseFloat(y0)
      ];

      return mousemove.pipe(
        map(toClientXY),
        toCSSTransform(),
        takeUntil(mouseup),
      );

    }),
  );

  return {
    class: 'draggable',
    onmousedown: dnd,
    style: dnd,
  };
};

Run on Stackblitz

The function above has the same shape: a simple function that returns an attribuend object.

It sets a class, then onmousedown, which will make it receive mousedown events and emits style, that will be a sink through which it will be able to emit updates back to the underlying element.

dnd is the reactive stream: an RxJS Subject that does a bit of coordinate calculations on any subsequent mouse movements until the button is released.

With Rimmel any DOM event can be bound directly to an observable stream, which makes it a zero-boilerplate joy to work with. No need to mess with addEventListener/removeEventListener, as it's all implicitly handled behind the scenes.

If you've not familiar with the way Drag'n'drop is implemented with RxJS, there are plenty of articles explaining how to do it and how it works, so I'd rather skip that part here.

Declarative = no access to the underlaying element

What is truly innovative here, compared to all other implementations is that mixins don't get a reference to the underlying element. This is not a limitation but a superpower, actually. It enables you to think in purely declarative terms, which makes this pattern greatly suited for testing, loose coupling and a strong separation of concerns. You can use one mixin just for drag and drop and other mixins for otcreate a Draggable mixin thather functionality that you can keep neatly isolated and well organised.

Sync vs Async

Image description
Ok, now the coolest part.
The examples above all set the specified properties/event listeners when when the component is mounted, synchronously.
It doesn't have to be like that. You can go async, as well. Both the mixin and every property of its attribuend object can be async objects (Promises or Observables!), so you can activate functionaliy based on a delay, or your custom activation logic and signals.

Suppose we want to make an element draggable on demand, depending on the state of a checkbox or any toggle element.

What you can create is an async mixin that takes a stream as a parameter and will dynamically change its behavior based on its latest emission (state)

const Draggable = async (trigger) => {

  // If an activation trigger was specified, just wait for it before activating
  trigger && await firstValueFrom(trigger);

  // so the Attribuend object is not emitted immediately.

  const dnd = new Subject<MouseEvent>().pipe(
    // the same drag'n'drop logic...
  );

  return {
    onmousedown: dnd,
    style: dnd,
  };

};

The above enables us to just connect a button to this mixin, so it can be made draggable after clicking it:

const trigger = new Subject<any>();

const template = rml`
  
${Draggable(trigger)}>

Run on Stackblitz

A quick summary

The following properties can be set using attribuend objects:

  • class
  • style
  • disabled
  • readonly
  • checked
  • innerHTML
  • innerText
  • appendHTML (a fictitious one, self-explaining)
  • on* (any event)
  • dataset (pass a dataset object)
  • dataWhatever (set a single dataset key-value)
  • otherAttribute (set any HTML attributes)

Getting creative

Attribute Mixins are a pretty novel pattern in JavaScript. Plenty of other use cases can be achieved. From activating functionality that's for logged-in users only, making content editable to admins (when they log in) to extending content with user-generated content or plugins. All without having to refresh the page!

Can you build anything clever with this novel pattern?
Try it out now and drop your stackblitz link in a comment below!

Learn More