Having investigated various approaches to address this issue, I have identified three distinct strategies based on your preference for maintaining type information within the specific ModelInterface
in CollectionInterface
, as well as specifying the desired type parameter for CollectionInterface
.
To begin with, let's rectify the typings for ModelInterface
and SerializedCollection
by leveraging generic constraints:
// Definition of a type that can be serialized into a M, where M represents a SerializedModel
interface ModelInterface<M extends SerializedModel> {
serialize(): M;
}
// Implementation of Contact serializing into SerializedContact
class Contact implements ModelInterface<SerializedContact> { /* ... */ }
// A serialization of collection of Ms
interface SerializedCollection<M extends SerializedModel> {
count: number;
items: M[];
}
Strategy 1: Introduce a separate type parameter for the ModelInterface
This method maintains the type information for specific ModelInterface
s within the CollectionInterface
. However, there were challenges with accurate type inference; explicit declaration of type parameters is required when constructing a Collection
.
The code snippet below demonstrates the use of parameter properties for Collection
, offering a more streamlined syntax than traditional class property assignment.
// Type capable of serializing into collections of Ms from the serializable Is
interface CollectionInterface<M extends SerializedModel, I extends ModelInterface<M>> {
count: number;
items: I[];
serialize(): SerializedCollection<M>;
}
// Class implementing CollectionInterface for a model M and model interface I
class Collection<M extends SerializedModel, I extends ModelInterface<M>> implements CollectionInterface<M, I> {
constructor(public items: I[], public count: number) {}
public serialize(): SerializedCollection<M> { /* ... */ }
}
// TypeScript automatically infers contactCollection as Collection<SerializedModel, Contact> if type parameters are not explicitly specified
const contactCollection = new Collection<SerializedContact, Contact>(
[new Contact("Bill"), new Contact("Bob")],
2
);
// Represents type Contact[]
const contacts = contactCollection.items;
Playground link
...