0

I have a module with a number of exported classes which all extend one base class. Each of these classes have a method called getOutput(). These classes match the structure of an API so I cant change it. The API returns a list and in that list can be any one of these classes (expect for the base class). I use the function to get a unified output no matter which type of object it is. The problem is, Typescript wont let you use the class function unless you assign the object to the class first. So in the base class I wrote a switch case to go through all the types and assign the object to a class. See here https://stackblitz.com/edit/angular-nvxmqe-9kxr2k

My problem is that using this approach means that the base class will have to know about each sub class. I would prefer that the subclass itself could assign its class based on the parameters in the class (the type parameter specifically). Or at the very least have another class do a dynamic lookup for the class based on the type. In Java I would use reflection to get a list of classes and assign them that way. I was wondering if there is something similar in Typescript. Or if there was a better way of doing this.

Again, the api is set, I cannot change it.

3
  • Could you provide a small working sample ? Commented Feb 26, 2018 at 20:40
  • Would generics work for you? Commented Feb 26, 2018 at 21:20
  • You can look at this serializer which handles inheritance based on a specific discriminator property in your object. type in your case. See kaiu-lab.github.io/serializer/classes/serializer.html Commented Feb 26, 2018 at 21:41

2 Answers 2

1

If you export a class className from a module moduleName and then import that module with the as syntax

import * as objects from "./moduleName";

Then the class className defined in moduleName is accessible like this

objects["className"]

So you can instantiate new instances from a class by name

let instance = new objects["className"];

I modified your stackblitz to illustrate this https://stackblitz.com/edit/angular-nvxmqe-wegfcf

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

7 Comments

Thanks, looked at it and this does exactly what I want. However, preferably I would like to include a method in the Base class to assign the objects. Just to keep it all in one spot. However, when I tried to do the import * as objects from "./objects"; in the actual object module it worked, but in Visual Code gives warnings for cyclical dependancy. Its also kinda of kludgey to import your a module into itself. Here is an example: stackblitz.com/edit/angular-nvxmqe-dlzf54?file=app/objects.ts . So is there a way to do this from within the module without needing the import line?
Why do you want to keep the method in the base class? It can just be a class factory, declared elsewhere.
just to keep everything related to the objects in one spot. Im coding this but other people might have to look at my code who dont really know javascript programming, let alone angular. Having the fact that the objects need to be assigned to an instance of the class in the same class as the base object just seems more intuitive to me and could save on some longer comments explaining everything.
I don't think you have to worry too much about people not knowing about angular, let alone js, when you code.. and if something is a bit tricky for some people to understand, add some coments in the code and even a link to this SO question. Here you should just move your method that creates instances to a helper class, or a factory class. This class should be in a different module, so that the import is not recursive
I went with this... but yes, I do have to worry about people who dont know about angular and barely know any javascript. Im writing software for a small company and I am the only web developer. There are other developers but they dont know much web. So Im trying to make it as straight forward as I can, in case they need to work on my code. Anyway, I explained it the best I could with comments so they will just have to figure it out if they come to it.
|
1

Edit

As mentioned by @David the below solution doesn't work as is with AOT. I got it to work by exporting the decorator function jsonType as well, but maybe it would be better to move it and Base into a module of their own.


I suggest to use the reflect-metadata package together with a decorator.

function jsonType(type: string) {
  return function<T>(Class: { new(...args: any[]): T}) {
    Reflect.defineMetadata('json-type', type, Class);
    return Class;
  }
}

export class Base {
    name: string;
    type: string;
    getOutput(): any { return this.name; }
}

@jsonType('object1')
export class Obj1 extends Base {
    property1: number;
    getOutput(): any { return this.name + ' ' + this.property1; }
}

@jsonType('object2')
export class Obj2 extends Base {
    property2: Date;
    getOutput(): any { return this.name + ' ' + this.property2; }
}

Then you can import all the exported classes instead of just Base

import * as objects from './objects'

and in ngOninit:

let v: objects.Base[] = JSON.parse('[{"name": "Ob1", "type": "object1", "property1": 4}, {"name": "Ob2", "type": "object2", "property2": "2018-02-02"}]');

v.forEach(o => {
  const objName = Object.keys(objects).find((key) => {
    const type = Reflect.getOwnMetadata('json-type', objects[key]);
    return type && type === o.type.toLowerCase();
  })
  const obj = objName && new objects[objName]();
  obj && this.list.push(Object.assign(obj, o));
})

I forked and edited your stackblitz.

Note, you could use Object.values instead of Object.keys but you need to load the lib in the tsconfig.

4 Comments

@David thanks for the hint. I tried it locally with ng server --aot and got it to work by exporting the decorator as well. Do you know why that is? I don't have much experience with AOT.
I don't have much experience either, but I remember trying a few things with custom decorators and could not get it to work with AOT. Did you try doing a ng build --aot? All good if it seems to be working though :)
@David I tried also to build it with AOT enabled as you suggested and it works. :)
thanks for the suggestion but it needs you to add a dependency and a lot more complexity than I think is warranted just to get the annotations to work. Its just as easy to match a static member and that way you dont add the dependency. Not that I was against adding it if it was needed. But I like to keep things as simple as they can be.

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.