Pete
Pete

Reputation: 3451

How to Model in TypeScript dynamic properties?

I want to somehow not have to to the ts-ignore when I use object properties referenced as string in TypeScript. That is I have a something defined like this:

const myList = ['aaa','bbb','ccc'];
const appContext = {};
for (let i=0;i<3,i++) {
  appContext[myList[i]] = 'newval' + i
}

If I setup my typescript type as

interface IAppContext {
  aaa: string;
  bbb: string
  ccc: string

I still get typescript errors. How do I handle this correctly?

Upvotes: 1

Views: 141

Answers (1)

jcalz
jcalz

Reputation: 328262

It would be nice if your example code were a true minimum reproducible example showing the exact errors and with no other typos or issues, but I think I understand what's happening.

The main problem you're likely to face is that myList is inferred by the compiler as type string[]. This is a reasonable guess for the compiler, because often people write things like const foo = ["a","b"] expecting to later alter the values inside the array like foo.push("c") (a const declaration only means you can't write foo = ... again, not that you can't set properties on it or call state-mutating methods on it).

To help the compiler when it makes the wrong inference on a literal value like ["aaa","bbb","ccc"] you can use a const assertion to ask the compiler to assume that the value will stay exactly as it is and not change. If you write const myList = ['aaa','bbb','ccc'] as const, the compiler will give myList the type readonly ["aaa", "bbb", "ccc"], a tuple of length 3 whose values cannot be rewritten and whose types are the exact string literal types "aaa", "bbb", and "ccc", in that order.

After that your code should mostly compile without warning as long as you assert that appContext is an IAppContext, like const appContext = {} as IAppContext. You can't merely annotate it like const appContext: IAppContext = {}, because that's not true (yet). It starts off empty, which is not a valid IAppContext. A type assertion is you telling the compiler not to worry about it, because even though appContext doesn't start out as a valid IAppContext, it will eventually be one after your loop runs. But keep in mind that it's your responsibility to make sure that it's properly initialized. If your myList were just ['aaa','bbb'], the code would still compile with no warnings and you'd be sad at runtime when you later call appContext.ccc.toUpperCase(). A type assertion is you taking the responsibility for the correct typing when the compiler can't verify it. So take the responsibility seriously.

Anyway, here's the resulting code, with no compiler errors:

interface IAppContext {
    aaa: string;
    bbb: string
    ccc: string
}
const myList = ['aaa', 'bbb', 'ccc'] as const; // const context to remember literals
const appContext = {} as IAppContext; // assert because it starts off empty
for (let i = 0; i < 3; i++) {
    appContext[myList[i]] = 'newval' + i
}

Hope that helps; good luck!

Playground link to code

Upvotes: 1

Related Questions