I have a number of classes that I’ll need to create instances of based on a string. Each class has a model that needs to be passed in their constructor and they are all different (some similar properties, but we’ll keep it simple).
I am trying to create a method that allows two parameters: one to determine what class is to be instantiated; and the second parameter should be narrowed to the model needed for that class.
For example if I pass MenuElementEnum.Products
as the first parameter, you should only then be allowed to pass a ProductModel
object as the second parameter.
<code>const MenuElementEnum = {
Categories = 'Categories',
class Product implements ProductModel{
constructor(model:ProductModel)
class Category implements CategoryModel{
constructor(model:CategoryModel)
[MenuElementEnum.Products]:Product,
[MenuElementEnum.Categories]:Category
type KeysInEnum = keyof typeof MenuElementEnum
type ModelTypeForNewElement<Z extends keyof typeof MenuElements, Type extends typeof MenuElements[Z]> = {
[Prop in keyof Type]: Type[Prop];
type GEN = keyof typeof MenuElementEnum;
class DynamicMenuElement {
static instance: DynamicMenuElement;
if (!DynamicMenuElement.instance) {
DynamicMenuElement.instance = new DynamicMenuElement();
return DynamicMenuElement.instance;
public createMenuElement<T extends GEN>(type: T, model: ModelTypeForNewElement<T,typeof MenuElements[T]>) {
const ElementType = MenuElements[type]
throw new Error(`Cannot find the class type of ${type}`);
return new ElementType(model);
const creator = DynamicMenuElement.get();
creator.createMenuElement(MenuElementEnum.Categories,/*code hinting says a Category [model] is required here (expected) */)
creator.createMenuElement(MenuElementEnum.Product,/*code hinting says a Product [model] is required here (expected) */)
<code>const MenuElementEnum = {
Categories = 'Categories',
Products = 'Products',
}
type ProductModel = {
name:string;
age:number
id:string;
}
type CategoryModel = {
name:string;
favorite:boolean
id:string;
}
class Product implements ProductModel{
name =''
age= 0;
id = '';
constructor(model:ProductModel)
{
}
}
class Category implements CategoryModel{
name=''
favorite=true
id=''
constructor(model:CategoryModel)
{
}
}
const MenuElements = {
[MenuElementEnum.Products]:Product,
[MenuElementEnum.Categories]:Category
}
type KeysInEnum = keyof typeof MenuElementEnum
type ModelTypeForNewElement<Z extends keyof typeof MenuElements, Type extends typeof MenuElements[Z]> = {
[Prop in keyof Type]: Type[Prop];
}
type GEN = keyof typeof MenuElementEnum;
class DynamicMenuElement {
static instance: DynamicMenuElement;
public static get() {
if (!DynamicMenuElement.instance) {
DynamicMenuElement.instance = new DynamicMenuElement();
}
return DynamicMenuElement.instance;
}
public createMenuElement<T extends GEN>(type: T, model: ModelTypeForNewElement<T,typeof MenuElements[T]>) {
const ElementType = MenuElements[type]
if (!ElementType) {
throw new Error(`Cannot find the class type of ${type}`);
}
return new ElementType(model);
}
}
const creator = DynamicMenuElement.get();
creator.createMenuElement(MenuElementEnum.Categories,/*code hinting says a Category [model] is required here (expected) */)
creator.createMenuElement(MenuElementEnum.Product,/*code hinting says a Product [model] is required here (expected) */)
</code>
const MenuElementEnum = {
Categories = 'Categories',
Products = 'Products',
}
type ProductModel = {
name:string;
age:number
id:string;
}
type CategoryModel = {
name:string;
favorite:boolean
id:string;
}
class Product implements ProductModel{
name =''
age= 0;
id = '';
constructor(model:ProductModel)
{
}
}
class Category implements CategoryModel{
name=''
favorite=true
id=''
constructor(model:CategoryModel)
{
}
}
const MenuElements = {
[MenuElementEnum.Products]:Product,
[MenuElementEnum.Categories]:Category
}
type KeysInEnum = keyof typeof MenuElementEnum
type ModelTypeForNewElement<Z extends keyof typeof MenuElements, Type extends typeof MenuElements[Z]> = {
[Prop in keyof Type]: Type[Prop];
}
type GEN = keyof typeof MenuElementEnum;
class DynamicMenuElement {
static instance: DynamicMenuElement;
public static get() {
if (!DynamicMenuElement.instance) {
DynamicMenuElement.instance = new DynamicMenuElement();
}
return DynamicMenuElement.instance;
}
public createMenuElement<T extends GEN>(type: T, model: ModelTypeForNewElement<T,typeof MenuElements[T]>) {
const ElementType = MenuElements[type]
if (!ElementType) {
throw new Error(`Cannot find the class type of ${type}`);
}
return new ElementType(model);
}
}
const creator = DynamicMenuElement.get();
creator.createMenuElement(MenuElementEnum.Categories,/*code hinting says a Category [model] is required here (expected) */)
creator.createMenuElement(MenuElementEnum.Product,/*code hinting says a Product [model] is required here (expected) */)
As you can see in this screenshot below, it’s merging “ProductModel” and “CategoryModel”. What I expected was:
<code>(model:ProductModel | CategoryModel) => Product | Category
<code>(model:ProductModel | CategoryModel) => Product | Category
</code>
(model:ProductModel | CategoryModel) => Product | Category
I’ve tried all sorts of different approaches. This is the closest I’ve come. I don’t know why the argument “model” is being declared as all of my type aliases squished together:
<code>(model:ProductModel | CategoryModel) => Product | Category
<code>(model:ProductModel | CategoryModel) => Product | Category
</code>
(model:ProductModel | CategoryModel) => Product | Category
and not:
<code>(model:ProductModel & CategoryModel) => Product | Category
<code>(model:ProductModel & CategoryModel) => Product | Category
</code>
(model:ProductModel & CategoryModel) => Product | Category