Xaniff Xaniff - 11 months ago 63
TypeScript Question

Storing generic object in private property on class

I'm writing an Angular 2 application and have a component that dynamically renders other components to the view as described at https://blog.thecodecampus.de/angular-2-dynamically-render-components/

That process works great, but rather than create the component and promptly destroy it from the constructor, I'd rather put that logic within another function in the class that determines exactly which component is expected to be written to the page based on an input. There exists functionality to switch which of these child components is shown, so I need to be able to store the resolved component object so I can call destroy() on it later on and this leads us to the problem at hand.

I would like to store this object on the class in a private property, but I can't figure out how to type it. Here's what the .d.ts file has to describe the createComponent function from which the object is originally instantiated:

abstract createComponent<C>(componentFactory: ComponentFactory<C>, index?: number, injector?: Injector, projectableNodes?: any[][]): ComponentRef<C>;

Now that's all well and good, but it's got a generic type of C that's resolved when I pass in the component that needs resolving, but I want to specify this to be any of the potential components I might pass in. I don't want to just specify my property as an 'any' because then the destroy down the road isn't going to validate, e.g.

private _visibleElement: any;

Now, sure I could typeguard the array and say that it can be of type AppleComponent | BananaComponent | CherryComponent etc, but I'd like this to be more flexible and not have to maintain those guards every time I modify this, e.g.

private _visibleElement: ComponentRef<AppleComponent|BananaComponent|CherryComponent>;

The only other approach I can think of would be to make an empty interface just for these components, have each implement it and then have that make up the type, but then I'm just switching the maintenance of the type guard to having to remember to apply the interface to each component I add in the future.

Is there any other approach I could take here that would allow me to specify that an object is of a generic type without actually having to specify in advance what that generic type is of the class?


Answer Source

The base interface must contain something, if it's empty then everything will be accepted:

interface MyComponent {}

function create<T extends MyComponent>(comp: T) {}

create(4); // no error
create({ key: "value" }); // no error

(code in playground)

Using this approach doesn't require you to remember to apply the interface on future classes, because the type safety will just remind you.
Once you have this base interface for all components, then make sure that you have functions/types that rely on that, this way if you introduce a new component class and it does not implement BaseComponent then the compiler will throw errors when you try to use it.
For example you can have:

type MyRef<T extends BaseComponent> = ComponentRef<T>;

And then use that everywhere:

abstract createComponent<C extends BaseComponent>(componentFactory: ComponentFactory<C>, index?: number, injector?: Injector, projectableNodes?: any[][]): MyRef<C>;