2

What I want is something like this :

class ThemeExample <T> {
    static theme : T;
    static init(theme : T){
        ThemeExample.theme = theme;
    }
};

//config it 
ThemeExample.init({
    hello : 'hi'
})
//in another file access properties with auto completion
ThemeExample.theme.hello

But this doesn't work. TS2302: Static members cannot reference class type parameters.

Tried a few other things such as making the class in a function and return it, and use functions generic in the class. Still couldn't find a way that would let me initialize this config once. And then access the theme later and get auto completion for all its properties in other files. I need something like this please help me, I'm too frustrated.

4
  • I doubt you'll get anything better than a factory function where you export what it returns. You can't easily mutate the state of a single variable, and even if you do that, there's no mechanism to have such state changes persist across file boundaries; you would need to export a const "configured version" and import it in different files. If that meets your needs I can write up an answer; otherwise I can only imagine a "this is impossible, sorry" answer. Commented May 1, 2021 at 14:08
  • @jcalz Thanks for the quick response, right now you have the function and export const in the same file.Could it be possible to do this but instead of creating and exporting in the same file, create in a file and export in another?Because this class is supposed to go in a module eventually the initialization however must stay on user's side. Commented May 1, 2021 at 14:45
  • ? Yes, the TS Playground only supports one file. If you want to provide me with a link to a minimal reproducible example web IDE project that I can fork and modify, I'll show you. You can export the factory function in your module, and in another file you can import the factory, create the class, and export that class, and in a third file you can import the created class. But in order to show this I need a multi-file IDE link Commented May 1, 2021 at 14:48
  • Thank you! codesandbox.io could do the job, but anyways if you post the example code as an answer that would be enough. Commented May 1, 2021 at 14:53

1 Answer 1

1

The static side of a class consists of the class constructor, and there is exactly one such constructor. It has no access to any generic parameters on the instance type, because a class constructor needs to be able to create instances for any possible specification of that generic parameter. In your ThemeExample<T> code, the class constructor must be able to make instances of, say, ThemeExample<string> and ThemeExample<number>. If you could have a static property of type T, it would mean that it would have to be both a string and a number and any other possible type for T. It doesn't work.

Conceptually you could imagine ThemeExample as having a static property named theme of type unknown, and then when you call ThemeExample.init("hello"), the compiler would narrow the ThemeExample class constructor so that theme is now of type string instead of unknown. But there is no good mechanism to do this. You could imagine writing something like an assertion function which would have such an effect:

interface ThemeExampleConstructor<T = unknown> {
  theme: T;
  init<T>(theme: T): asserts this is ThemeExampleConstructor<T>;
  new(): ThemeExample<T>;
}

declare const ThemeExample: ThemeExampleConstructor;

ThemeExample.theme.toUpperCase(); // error
ThemeExample.init("hello");
ThemeExample.theme.toUpperCase(); // okay
new ThemeExample().instanceProp.toUpperCase(); // okay

But you can't do it with class syntax, and even if you got this done, narrowing like this doesn't work across file boundaries or even function boundaries. There's no mechanism whereby the compiler would see that ThemeExample.init("hello") in one file happens before the ThemeExample.theme.toUpperCase() in another file. So we can't do this at all.


Instead I'd suggest going the route of a factory function which produces new class constructors. This function would exist in some library, say Library/index.ts:

export function initThemeExample<T>(theme: T) {
    return class ThemeExample {
        static theme = theme;
    };
}

Then in another file like userLib.ts, a user could import that from the library, configure, and re-export:

import {initThemeExample} from './Library'

//config it 
export const ThemeExample = initThemeExample({
    hello: 'hi'
})

And then the user could use this configured constructor in all other files:

import {ThemeExample} from './userLib'

//import your ThemeExaple in another file
document.write(ThemeExample.theme.hello.toUpperCase()); // HI

Link to StackBlitz code

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

1 Comment

Disappointed that this is not possible after one day of trying. But your alternative is brilliant and creative.I use it as it is the best option currently available. Thank you so much.

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.