Creating variable Stimulus target types in TypeScript at runtime

Currently, I am developing stimulus controllers in TypeScript and I can't help but wonder if there is a more efficient way to declare all the properties like has*Target, *Target, and *Targets. It feels tedious to declare each one individually.

Is there a better approach that achieves what I'm aiming for? Any insights would be greatly appreciated. Thank you!

Below is some code snippet that I have been experimenting with in an attempt to make it work:

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

namespace Transform {
  export type HasTarget<T> = {
    [K in keyof T as `has${Capitalize<string & K>}Target`]: boolean;
  };
  export type Target<T> = {
    [K in keyof T as `${string & K}Target`]: T[K];
  };
  export type Targets<T> = {
    [K in keyof T as `${string & K}Targets`]: Array<T[K]>;
  };
}

const targets = [
  'button',
  'container',
] as const;

type TargetList = typeof targets;

interface Targets extends Record<TargetList[number], HTMLElement> {
  button: HTMLButtonElement;
  container: HTMLDivElement;
}

type ControllerWithTarget<T> =
  & Transform.HasTarget<T>
  & Transform.Target<T>
  & Transform.Targets<T>;


// Attempting to achieve something similar to this
export default class extends Controller implements ControllerWithTarget<Targets> {
  static targets = [...targets];

  // declare readonly hasButtonTarget: boolean;
  // declare readonly buttonTarget: HTMLButtonElement;
  // declare readonly buttonTargets: Array<HTMLButtonElement>;

  // declare readonly hasContainerTarget: boolean;
  // declare readonly containerTarget: HTMLDivElement;
  // declare readonly containerTargets: Array<HTMLDivElement>;

  disableButton(): void {
    if ( this.hasButtonTarget ) {
      this.buttonTarget.disabled = true;
    }
  }
}

UPDATE The compiler is showing an error message:

Class 'default' incorrectly implements interface 'ControllerWithTarget<Targets>'.

Answer №1

This is a demonstration of how I would apply it:

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

namespace Transform {
  export type HasTarget<T> = {
    [K in keyof T as `has${Capitalize<string & K>}Target`]: boolean;
  };
  export type Target<T> = {
    [K in keyof T as `${string & K}Target`]: T[K];
  };
  export type Targets<T> = {
    [K in keyof T as `${string & K}Targets`]: Array<T[K]>;
  };
}

const targets = [
  'button',
  'container',
] as const;

interface Targets extends Record<typeof targets[number], HTMLElement> {
  button: HTMLButtonElement;
  container: HTMLDivElement;
}

type ControllerWithTarget<T> =
  & Transform.HasTarget<T>
  & Transform.Target<T>
  & Transform.Targets<T>;

export default class extends Controller implements ControllerWithTarget<Targets> {
  static targets = [...targets];

  disableButton(): void {
    if (this.hasButtonTarget) {
      this.buttonTarget.disabled = true;
    }
  }
}

Similar questions

If you have not found the answer to your question or you are interested in this topic, then look at other similar questions below or use the search

Unveiling the Power of Angular2: Enhancing Functionality

Struggling to implement a lowercase pipe in angular2 and encountering syntax errors with ';' syntax errors got ';' app/lowercase.ts import {Pipe, PipeTransform} from '@angular/core'; @Pipe({ name: 'lowercase1'} ...

Implement the Promise return type for a function in Angular

I'm looking to modify the following method to change the return type to a Promise or Observable: basketItemNodes: TreeNode[] = []; getBasketItems() { this.basketService.getAllBasketItems() .subscribe( res => { this.basketItemN ...

Collapse an array containing objects with nested objects inside

Similar questions can be found here and here, but I am struggling to modify the code provided in the answers to fit my specific needs due to its conciseness. The structure of my array of objects is as follows: [ { id: 1, someProp: &quo ...

The Next.js app's API router has the ability to parse the incoming request body for post requests, however, it does not have the

In the process of developing an API using the next.js app router, I encountered an issue. Specifically, I was successful in parsing the data with const res = await request.json() when the HTTP request type was set to post. However, I am facing difficulties ...

The customer's status cannot be determined

I've encountered an issue with my TypeScript code that includes a simple if-else logic. endDate: String = ''; customerStatus: String; this.endDate = this.sampleData.customerStartDate; if (this.endDate == null) { this.customerStatus = ' ...

What is the process for updating Typescript to the most recent version?

My MacBook Pro is running OS X 10.8.2 and I previously installed Typescript. Now, I want to update it to the latest version (v0.8.3). I used this command in the terminal: sudo npm install -g typescript However, the output displayed was: npm http GET ht ...

Error message in Typescript: The argument type '() => () => Socket<DefaultEventsMap, DefaultEventsMap>' cannot be assigned to a parameter of type 'EffectCallback'

I am struggling to comprehend how I should specifically type constrain in order to prevent the error within my useEffect. One approach is to either cast my newSocket or specify the return value of my useEffect as any, but I am hesitant about that solution. ...

Encountering build errors while utilizing strict mode in tsconfig for Spring-Flo, JointJS, and CodeMirror

While running ng serve with strict mode enabled in the tsconfig.json, Spring-Flow dependencies are causing errors related to code-mirror and Model. https://i.sstatic.net/KUBWE.png Any suggestions on how to resolve this issue? ...

Designing a versatile Angular component for inputting data (Mailing Address)

Currently, I am in the process of developing an Angular 11 application that requires input for three distinct mailing addresses. Initially, I thought I had a clear understanding of what needed to be done, only to encounter warnings about elements with non- ...

Using a variety of objects in TypeScript arrays

How to effectively implement a superior class in TypeScript for an array containing diverse objects that inherit from the same class, without triggering errors? This is my attempt: interface IVehicle{ modelName: string } interface ICar extends IVehi ...

Implementing NGRX sequential actions triggered from a component

I am attempting to send sequential requests to the ngrx-store in order to save two different entities in randomly chosen MongoDB collections. The challenge lies in needing a response from the first ngrx-store effect in order to utilize the data in another ...

Creating a personalized design for MUI TextField spin button

Looking to customize the appearance of the up/down spin buttons in MUI TextField. https://i.sstatic.net/DcG66.png Desiring white arrows and a black surrounding area that's slightly larger, akin to this: https://i.sstatic.net/ZxMJw.png I'm aware ...

Tips for securely encrypting passwords before adding them to a database:

While working with Nest.Js and TypeORM, I encountered an issue where I wanted to hash my password before saving it to the database. I initially attempted to use the @BeforeInsert() event decorator but ran into a roadblock. After some investigation, I disc ...

Issue with Undefined Variable in Angular 2 and Ionic Framework

I included the following code in my HTML file: <ion-col col-3 align="right"> <ion-item> <ion-label>Show as</ion-label> <ion-select [ngModel]="SelectedView" (ngModelChange)="onViewChange($eve ...

Enhance Summernote functionality by creating a custom button that can access and utilize

Using summernote in my Angular project, I am looking to create a custom button that can pass a list as a parameter. I want to have something like 'testBtn': this.customButton(context, listHit) in my custom button function, but I am unsure how to ...

Typescript error message TS2693: The identifier 'Note' is used as a value although it refers to a type

I have been attempting to incorporate Typescript into my Firebase project, but unfortunately I am encountering the following errors: error TS2693: 'Note' only refers to a type, but is being used as a value here. The code snippet I used is as fo ...

Verify additional keys in the output of a function

Imagine this scenario: type Keys = 'x' | 'y' | 'z' type Items = { [K in Keys]?: number } let items: Items = { x: 1, y: 4 } The result is: Type '{ x: number; y: number; }' cannot be assigned to type 'Items' ...

How can you create a unique record by appending a number in Javascript?

Currently, when a file already exists, I add a timestamp prefix to the filename to ensure it is unique. However, instead of using timestamps, I would like to use an ordinal suffix or simply append a number to the filename. I am considering adding an incr ...

This browser does not recognize the tag <>. To render a React component, ensure its name starts with an uppercase letter

MyIcons.tsx export const ICONCAR = () => ( <span className="svg-icon svg-icon-primary svg-icon-2x"><svg xmlns="http://www.w3.org/2000/svg" xmlnsXlink="http://www.w3.org/1999/xlink" width="24px" height=&qu ...

Create and export a global function in your webpack configuration file (webpack.config.js) that can be accessed and utilized

Looking to dive into webpack for the first time. I am interested in exporting a global function, akin to how variables are exported using webpack.EnvironmentPlugin, in order to utilize it in typescript. Experimented with the code snippet below just to und ...