0

I have a class Foo, and I want to extend it dynamically with some literal object that I have. How can I keep the this typed without manually repeating the keys of the literal object in a separate interface?

const someLiteralObject = {
  key1: 'foo',
  key2: 'bar',
  //  ...lots of more keys
}

class Foo {
   constructor(){
      Object.assign(this, someLiteralObject)
   }
   test() {
    console.log(this.key1); //error: Property 'key1' does not exist on type 'Foo'
   }
}

const test = new Foo();
console.log(test.key1); //error: Property 'key1' does not exist on type 'Foo'

2 Answers 2

2

EDIT: I suggest you to look into Mixins in Typescript and research the difference between Object.assign and applyMixins helper presented there. Perhaps that design pattern is what you actually need instead.

Here's a dirty but strongly typed solution wit using a literal object and not repeating all it's keys as asked. Explanations inline and TypeScript Playground link.

// Hide these helpers away in your `utils.ts` somewhere, see below what they do.
/**
 * Gives Constructor given a instance, like inverse of `InstanceType`.
 *
 * Second optional parameter is argument tuple for the `constructor()`.
 *
 * @todo this interface lacks signature for static methods/properties.
 */
export interface IConstructor<T extends object = object, TA extends unknown[] = unknown[]> {
    new(...args: TA): T
}
/**
 * Overrrides a class to return a instance that includes the given mixin.
 */
export type ClassWithMixin<T extends IConstructor, TMixin extends object> =
    IConstructor<InstanceType<T> & TMixin, ConstructorParameters<T>>


// A mixin many keys and/or methods. Use `typeof someLiteralObject` to use it as interface.
const someLiteralObject = {
  key1: 'foo',
  key2: 'bar',
} as const  // <-- `as const` is optional, but handy.

// `this:` type tells method internal scope that `this` is more than TS thinks by default.
class FooPure {
   constructor(){
      Object.assign(this, someLiteralObject)
   }
   test(this: this & typeof someLiteralObject) {
    console.log(this.key1)
   }
}
// And `ClassWithMixin` type tells all other codebase that `Foo` is more than `FooHidden`.
const Foo = FooPure as ClassWithMixin<typeof FooPure, typeof someLiteralObject>

// Works as expected.
const foo = new Foo()
console.log(foo.key1)
foo.test()
class Bar extends Foo {
  constructor() {
    super()
    this.test()
  }
}
console.log(new Bar())
Sign up to request clarification or add additional context in comments.

2 Comments

works! can you explain how Constructor and ExtendedWith types work?
@kundasaba I added explanation to types, as well suggested a mixin pattern. Please consider accepting answer if that answers your question fully.
0

Strongly typed way:

class Some { // <= This is representing your Object
        key1: string;
        key2: string;
}

const someLiteralObject: Some = { // <= As you can see it is of type Some
  key1: 'foo',
  key2: 'bar',
}

class Foo extends Some { // <= Tell TS we are using all the keys from Some
   constructor(){
      super(); // <= needed for ts, useless as we did not define a constructor in Some
      Object.assign(this, someLiteralObject);
   }
   test() {
    console.log(this.key1); // Yeah!
   }
}

const test = new Foo();
console.log(test.key1); // No error!
test.test(); // works fine!

Hacky way (e.g. when you dont know what keys the object will have)

const someLiteralObject = {
  key1: 'foo',
  key2: 'bar',
}

class Foo {
   constructor(){
      Object.assign(this, someLiteralObject)
   }
   test() {
    console.log((this as any).key1); // tell ts to be unsure about the type of this
   }
}

const test = new Foo();
console.log((test as any).key1); // tell ts to be unsure about the type of this

1 Comment

the list of props of the literal object is very big, is there any way to avoid the repetition between Some and someLiteralObject?

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.