0

I am using typescript and want to write a factory function to create a new object instance and provide an array of properties to be filled. Basically a converter from an existing object to a new instance of another (related) type. It should be used like this

const newA = A.from({ a: 1 });
const newC = C.from({ a: 1, b: 2, c: 9 });

I have this working (member variables omitted for clarity and Object.create could be simpler, see below for the reason)

class A {
  static from(data: Partial<A>) : A {
    const o = Object.create(A);
    Object.assign(o, data);
    return o;
  }
}

This works nicely as long as I inherit from this class. I do not want to recreate this function anytime like so

class A {
  static from(data: Partial<A>) : A {
    const o = Object.create(A);
    Object.assign(o, data);
    return o;
  }
}

class B extends A {
static from(data: Partial<B>) : B {
    const o = Object.create(B);
    Object.assign(o, data);
    return o;
  }
}

The following snippet works, but I need to specify the class when I call it twice

class A {
  ...
  static from<T extends A>(data: Partial<T>) : T {
    const o = Object.create(this);
    Object.assign(o, data);
    return o;
  }
}

const newC = C.from<C>({ ... });

and want to use some form of generic, i.e. I imagine something like this.

class A {
  static from<T extends this>(data: Partial<T>) : A {
    const o = Object.create(this);
    Object.assign(o, data);
    return o;
  }
}

const new C = C.from({ ... });

I would guess that this is theoretically possible, since at compile time the context of the static function is known and you could infer the class type, but I did not figure out on how to use this inherited type in the signature definition.

Is this a good (acceptable) pattern and if not, what should I do?

Any help is appreciated!

1 Answer 1

3

You're looking for polymorphic this types for static methods, but they are not currently supported in TypeScript. There's an open feature request for it, microsoft/TypeScript#5863 but I don't know if it will ever be implemented.

Luckily there are workarounds, one of which is mentioned here: use a generic method in which the this parameter depends on the generic. For example:

class A {
    static from<T extends A>(this: new () => T, data: Partial<T>): T {
        const o = new this(); // no-arg constructor
        Object.assign(o, data);
        return o;
    }
}

Here we will only allow from() to be called on objects which are no-arg constructors of some subtype of A, and they will return this subtype. I changed the implementation from Object.create(this) to new this() just to show that we care about the "no-arg-ness" (after all, a subclass called with Object.create(this) that required a constructor argument is likely to fail to construct a valid instance).

So then this works, since B is a no-arg constructor of B instances:

class B extends A {
    foo = "bar";
}
const b = B.from({}); // B
console.log(b.foo.toUpperCase()); // BAR

but this fails, since Z is not a no-arg constructor of Z instances:

class Z extends A {
    constructor(public bar: string) {
        super();
    }
}
const z = Z.from({}); // error!
// -----> ~
// typeof Z is not assignable to new () => Z
console.log(z.bar.toUpperCase());  // error at runtime, z.bar is undefined

Okay, hope that helps; good luck!

Playground link to code

Sign up to request clarification or add additional context in comments.

2 Comments

That was insanely enlighting! Thank you. I am still struggeling as to why you can add the this parameter to the signature and it is ignored in the function call? Is this a special type of parameter?
Yes, exactly. It’s called a “this parameter” and I’ve linked to the relevant documentation in my answer. Here’s the link again: this parameters

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.