Home > Blockchain >  TypeScript: Instantiate class dynamically and add to DOM element
TypeScript: Instantiate class dynamically and add to DOM element

Time:08-05

I'm new to TypeScript and I'm trying to convert my own way of binding (vanilla) JS components to DOM elements into TypeScript, but I'm struggling hard. Here's what I have so far. While I understand why the compiler is complaining, I don't know how to solve it.

index.ts (entry point)

import App from './App'
import bindings from './bindings'

const app = new App();
app.bind(bindings);

bindings.ts

import Foo from './components/Foo';

type Binding = {
  component: Function;
  name: string;
  options?: object;
}

export default [
  { component: Foo, name: 'foo' }, // Would get bound to .js-foo elements
] as Binding[];

App.ts (truncated for brevity)

export default class App
{
  bind(bindings: Binding[] = []) {
    for (const binding of bindings) {
      document.body.querySelectorAll(`.js-${binding.name}`).forEach($el => {
        // Initialize a custom object on the DOM element to store component instance
        const app: { [key: string]: object } = {};
        $el.app = $el.app ?? app

        // What I'm trying to accomplish in non type checking code:
        $el.app[binding.name] = new binding.component(binding.options ?? {});
        // Left-hand error: Element implicitly has an 'any' type because expression of type 'string' can't be used to index type '{}'.
        //   No index signature with a parameter of type 'string' was found on type '{}'.ts(7053)
        // Right-hand error: This expression is not constructable.
        //   Type 'Function' has no construct signatures.ts(2351)
      });
    }
  }
}

Any help is very much appreciated!

CodePudding user response:

Left-hand error:

TS is complaining because $el.app does not necessarily have a key with the arbitrary name from binding.name. You are setting a valid type for this usecase when defining the const app, but there's not a guarantee that $el.app will take that value, due to the nullish operator.

The brute-force solution is to simply cast $el.app:

$el.app = ($el.app ?? {}) as { [key: string]: object };

Right-hand error:

Function and classes are handled differently in typescript. If you want a generic class, you can use this type:

 type ClassConstructor = new (...args: any[]) => unknown;

Or better yet, create a base class, and use it as the type:

 class BaseBindingComponent {}

. . .

 class Foo extends BaseBindingComponent {}

. . .

type Binding = {
  component: BaseBindingComponent;
  name: string;
  options?: object;
}

  • Related