Stimulus and Typescript

Hi all,

I’m trying out stimulus for on of my applications. This one still has a lot of dependency on jquery, so it will be nice to finally be able to remove that from the bundle. The application is written in typescript, since stimulus is also written in typescript this shoudln’t be too difficult.

One of the things I’m having trouble with is declaring this.element. Since this.element is defined as a getter in the parent class, the typescript compiler doesn’t want me to overwrite that declaration.
When one of my controllers depends on a specific HTML Element, I currently always have to cast it to that element.
For example:

export default class extends Controller {  
 get value(): string {
    return (this.element as HTMLInputElement).value;
  }
}

This doesn’t seem very DRY to me. Am I missing something?
It doesn’t seem like there is a way to provide the Controller class with a generic, since the type is set as Element: stimulus/context.ts at dc101fc5337c49b2fbcc0b43e08a61e9b4c8991d · hotwired/stimulus · GitHub

2 Likes

I have similar problems with:

export default class extends Controller {
    element: HTMLButtonElement;
}

Which ends in:

TS2564: Property 'element' has no initializer and is not definitely assigned in the constructor.
TS2610: 'element' is defined as an accessor in class 'Controller', but is overridden here in 'default' as an instance property.

By changing to:

export default class extends Controller {
    declare element: HTMLButtonElement;
}

I still get then this one:

TS2610: 'element' is defined as an accessor in class 'Controller', but is overridden here in 'default' as an instance property.

If I try to use:


export default class extends Controller {
    declare get element(): HTMLButtonElement;
}

It ends in this error:

TS1031: 'declare' modifier cannot appear on class elements of this kind.

Could you solve that problem?

My current solution is making my own AbstractController which uses Generics and ignores that error:

import { Controller } from '@hotwired/stimulus';

export default abstract class AbstractController<StimulusElement extends Element> extends Controller {
    // @ts-ignore see https://discuss.hotwired.dev/t/stimulus-and-typescript/2458
    declare readonly element: StimulusElement;
}
import AbstractController from '../src/AbstractController';

export default class extends AbstractController<HTMLButtonElement> {
    static values = {
        code: String,
    };

    static targets = [
        'name'
    ];

    declare readonly codeValue: string;
    declare readonly nameTarget: HTMLInputElement
}

I hope this maybe helps someone else. If somebody know a better solution I’m happy to update the example.

I’ve submitted a pull request to improve this in Stimulus directly.

1 Like

@rik I also like that you did set a default with

Controller<ElementType extends Element = Element> {

did not know that this is possible :+1: