I have some feedback and ideas for Stimulus after using it in a project

Hello everyone,

I’ve been using Stimulus in a medium sized project, that contains about maybe 20 controllers so far. Now I’ve gained some experience with Stimulus and learned how (and if) it works. In the beginning, I was using Vue Custom Elements, but I’ve noticed that you just can’t push everything into custom elements. That’s why I switched over to Stimulus for said project.

It fits the project structure a lot better, but also I was missing some features and honestly I’m not super happy about a few decisions. I thought about creating my own framework. (I’ve drafted an HTML and JS API idea so far) Since it’s not a React/Vue/Angular sized framework I think it’s doable. But then I read about the plans of version 1.2 and I thought about it again because the 1.2 plan contains some features I was missing. So instead I want to share some of my ideas and also other feedback first and listen to what you think about it. And I do know that most changes will need a major version bump since most of my ideas would be rather big changes.

The HTML side

Controller

On one side I like how data-controller looks like ng-controller from Angular.js 1.x ‒ in general, it reminds me a lot of ng1, but in a good way ‒ but on the other side I think it’s a little too much in this context. Instead I’d use a special type of class.

I.e. instead of <div data-controller="my-controller"> it could be <div class="@my-controller">. It’s a lot shorter since most elements will have classes anyway. With this change, the data- attributes will contain only real data. Controllers are more like access points. That’s what classes are by nature. I think the @ is fine since you shouldn’t access those classes in CSS - but you still could.

Targets

Based on the idea above targets could be declared like this: <div class="@my-controller:my-target">. (I don’t know why, but a colon looks better to me than a dot. But I’d also take a dot.)

Independent of this new syntax: To me, it feels rather strange to write element identifiers in HTML in camelCase. Classes, attributes, custom elements… all are written in kebab-case. That’s one reason I write my own getters in the controller classes. That way I can use kebab-cased targets.

Actions

Honestly… I think they are very unnecessary. While drafting my own framework I’ve noticed that .addEventListener doesn’t work. That’s why an alternative is necessary, yes. But I think that should live in the controller class. If you bind events to targets you could still move those elements around by moving the target.

Also: in our team, the HTML is being used and built by backend developers and I’d totally understand if they told me they don’t care which exact events need to be listened to, which exact method needs to be called. They don’t even know which methods can be called and which methods are meant to be event callbacks. It doesn’t make sense to call a function that expects parameters other than an event object. But currently, you can do that and break a lot of stuff.

That’s why I think it should be my job as a JS developer to take care of event listeners. And I work inside JS, not HTML.

How? I think there are many ways to do that. Maybe with a target definition object, or with a function to bind events to targets (Stimulus.addEvent('some-target', 'click', this.onClick)). Global events also make more sense here than in arbitrary HTML elements.

I think decorators are predestined to do that (class extends Controller { @Stimulus.on('click') onClick (event) {} }). But I understand if Stimulus doesn’t want to go that fast - especially since the specification has changed. Maybe that’s something for a future version.

Another crazy idea I had is as follows:

class extends Controller {
  [Stimulus.event('click', 'some-target')] (event) {
    console.log('some-target got clicked')
  }
}

The event() method registers the event and returns a Symbol or a unique string to identify, access and call the method itself. I’m not 100% sure if that even works. But it looks very much like the decorator version.

The new Classes API

I totally love it (and I had the exact same idea in my draft). But I think it shouldn’t be called that. You could use a map for several things like wordings/translations, names of icons, images etc. That’s pretty versatile indeed. I noticed this just recently.

But I also wonder if this is really necessary. You could implement a class API including other maps like mentioned above very easily through the also new Value API and a custom type, or couldn’t you? That makes the Classes API kind of redundant if I’m not missing something.

The JS side

Targets

Is it really necessary that this.someTarget throws an error if there’s no such target? This hit me by surprise since I expected it to just return null. null is also easier to check, especially if you’re using TypeScript. Errors, on the other hand, are not part of a type (as far as I know). Also, accessing a target twice (this.hasSomeTarget + this.someTarget) every time feels weird. If the accessor returns null there’s also no need for the this.has[name] accessor. Checking for null or just putting the accessor as is in an if () statement would have the same effect.

Internal properties as Symbols

I’ve never seen this before but I don’t know why. Wouldn’t it be a good idea to access framework methods through symbols? That way you don’t block property names in controller classes. E.g.

import { Controller, connect, targets } from '@stimulus/core'

class MyController extends Controller {
  [connect] () {
    console.log('some target:', this[targets].find('some'))
  }

  connect () {
    console.log('I am a user defined connect method.')
  }
}

That’s it so far. What do you think about my ideas?

Greets
Andreas

1 Like