Reputation: 5611
I have a type which is a disjunction of strings:
export type Category = 'people' | 'projects' | 'topics' | 'tools'
I declare an index as:
interface Entry {
...
}
type IPostEntryIndex = {
[name in Category]: Entry
}
How do I declare class properties corresponding to the category names?
const categories: Category[] = ['people', 'projects', 'topics', 'tools']
class PostEntryIndex implements IPostEntryIndex {
// what goes here so that `PostEntryIndex` has an attribute
// for each category in Category?
constructor () {
categories.map(cat => this[cat] = new Entry())
}
}
Update: of course I can simply declare the categories explicitly.
What I'm looking for is a DRY answer, not requiring update if I add
a category. (NB making categories
array not repeat Category
would
also be nice. I guess I could use an enum for that.)
Upvotes: 1
Views: 291
Reputation: 329573
There's no syntax that declares a bunch of properties in a class, so far as I know. You can add an index signature this way, but not a union of string literals. Obviously you can add them manually, but I presume the point is that you don't want to.
One thing you can do is to use declaration merging to add the properties into the class instance's interface. When you write class PostEntryIndex {}
it creates a value named PostEntryIndex
which is a class contructor, and an interface named PostEntryIndex
corresponding to the class index. And you can use interface merging to add whatever properties you want... like this:
interface PostEntryIndex extends IPostEntryIndex { }
class PostEntryIndex {
constructor() {
categories.map(cat => this[cat] = new Entry())
}
}
Note that doing this circumvents the strict class property initialization checking that comes with --strict
mode, so you can't rely on the compiler to complain about uninitialized properties. (This is actually being used to your advantage here, since the compiler does not understand that looping over categories
serves to initialize all the relevant properties.)
Also note that once you merge the interface in that way, the implements
clause is particularly redundant so I got rid of it. (It turns out that implements
clauses are never really necessary; TypeScript's structural type system does not require you to declare that A implements B
for you to use an A
as a B
, as long as they have compatible structures... but I digress)
Anyway let's see if it works:
const pei = new PostEntryIndex();
pei.projects; // Entry
Looks good. Okay, hope that helps; good luck!
Upvotes: 1
Reputation: 3081
The following is accepted by https://www.typescriptlang.org/play/
export type Category = 'people' | 'projects' | 'topics' | 'tools'
class Entry {
}
type IPostEntryIndex = {
[name in Category]: Entry
}
const categories: Category[] = ['people', 'projects', 'topics', 'tools']
class PostEntryIndex implements IPostEntryIndex
{
people: any;
projects: any;
topics: any;
tools: any;
constructor () {
categories.map(cat => this[cat] = new Entry())
}
}
Upvotes: 0