0
# Generic Classes
1
2
Specialized mixing support for generic classes using the decorator pattern. Due to TypeScript limitations with generic parameters in base class expressions, generic class mixing requires a different approach using the `mix` decorator combined with declaration merging.
3
4
## Capabilities
5
6
### Mix Decorator
7
8
A decorator version of the Mixin function specifically designed for mixing generic classes.
9
10
```typescript { .api }
11
/**
12
* A decorator version of the Mixin function for mixing generic classes
13
* Used when generic parameters prevent using Mixin in extends clause
14
* @param ingredients - Array of class constructors to mix
15
* @returns Class decorator function
16
*/
17
function mix(...ingredients: Class[]): (decoratedClass: any) => any;
18
```
19
20
**Usage Pattern:**
21
22
The `mix` decorator must be used in combination with declaration merging to provide proper typing:
23
24
```typescript
25
import { mix } from "ts-mixer";
26
27
// Base generic classes to mix
28
class Foo<T> {
29
public fooMethod(input: T): T {
30
return input;
31
}
32
}
33
34
class Bar<T> {
35
public barMethod(input: T): T {
36
return input;
37
}
38
}
39
40
// Declaration merging: interface provides the typing
41
interface FooBar<T1, T2> extends Foo<T1>, Bar<T2> {}
42
43
// Decorator provides the runtime behavior
44
@mix(Foo, Bar)
45
class FooBar<T1, T2> {
46
public fooBarMethod(input1: T1, input2: T2) {
47
return [this.fooMethod(input1), this.barMethod(input2)];
48
}
49
}
50
51
// Usage
52
const instance = new FooBar<string, number>();
53
const result = instance.fooBarMethod("hello", 42);
54
// result: ["hello", 42]
55
// TypeScript knows fooMethod and barMethod are available
56
```
57
58
## Key Differences from Regular Mixin
59
60
### Declaration Merging Requirement
61
62
Unlike the regular `Mixin` function, `mix` requires explicit declaration merging:
63
64
```typescript
65
// This interface declaration is REQUIRED
66
interface MixedClass<T1, T2> extends BaseClass1<T1>, BaseClass2<T2> {}
67
68
// The decorator provides runtime behavior but no typing
69
@mix(BaseClass1, BaseClass2)
70
class MixedClass<T1, T2> {
71
// Your additional methods here
72
}
73
```
74
75
### No Type Inference
76
77
The `mix` decorator does not provide automatic type inference like `Mixin`. You must manually specify the interface that extends all mixed classes.
78
79
### Runtime vs Compile-time
80
81
- **Interface declaration**: Provides TypeScript typing at compile-time
82
- **@mix decorator**: Provides actual mixin behavior at runtime
83
- Both are required for generic class mixing to work properly
84
85
## Advanced Usage Examples
86
87
### Multiple Generic Parameters
88
89
```typescript
90
import { mix } from "ts-mixer";
91
92
class Storage<T> {
93
private items: T[] = [];
94
95
store(item: T): void {
96
this.items.push(item);
97
}
98
99
retrieve(): T[] {
100
return [...this.items];
101
}
102
}
103
104
class Validator<T> {
105
private rules: ((item: T) => boolean)[] = [];
106
107
addRule(rule: (item: T) => boolean): void {
108
this.rules.push(rule);
109
}
110
111
validate(item: T): boolean {
112
return this.rules.every(rule => rule(item));
113
}
114
}
115
116
class Logger<T> {
117
log(message: string, item?: T): void {
118
console.log(`[LOG]: ${message}`, item);
119
}
120
}
121
122
// Declaration merging for all three generic classes
123
interface ValidatedStorage<T> extends Storage<T>, Validator<T>, Logger<T> {}
124
125
@mix(Storage, Validator, Logger)
126
class ValidatedStorage<T> {
127
storeWithValidation(item: T): boolean {
128
this.log("Attempting to store item", item);
129
130
if (this.validate(item)) {
131
this.store(item);
132
this.log("Item stored successfully");
133
return true;
134
} else {
135
this.log("Item validation failed");
136
return false;
137
}
138
}
139
}
140
141
// Usage
142
const storage = new ValidatedStorage<number>();
143
storage.addRule(x => x > 0);
144
storage.addRule(x => x < 100);
145
146
storage.storeWithValidation(50); // true, logs success
147
storage.storeWithValidation(-5); // false, logs validation failure
148
149
const items = storage.retrieve(); // [50]
150
```
151
152
### Generic Class Hierarchies
153
154
```typescript
155
import { mix } from "ts-mixer";
156
157
// Base generic class
158
class Container<T> {
159
protected items: T[] = [];
160
161
add(item: T): void {
162
this.items.push(item);
163
}
164
}
165
166
// Another generic class extending Container
167
class SortedContainer<T> extends Container<T> {
168
add(item: T): void {
169
super.add(item);
170
this.sort();
171
}
172
173
private sort(): void {
174
this.items.sort();
175
}
176
}
177
178
// Mixin class with different generic parameter
179
class Timestamped<U> {
180
private timestamps: Map<U, Date> = new Map();
181
182
setTimestamp(key: U, date: Date = new Date()): void {
183
this.timestamps.set(key, date);
184
}
185
186
getTimestamp(key: U): Date | undefined {
187
return this.timestamps.get(key);
188
}
189
}
190
191
// Mixing a class hierarchy with another generic class
192
interface TimestampedSortedContainer<T, U>
193
extends SortedContainer<T>, Timestamped<U> {}
194
195
@mix(SortedContainer, Timestamped)
196
class TimestampedSortedContainer<T, U> {
197
addWithTimestamp(item: T, key: U): void {
198
this.add(item);
199
this.setTimestamp(key, new Date());
200
}
201
}
202
203
// Usage
204
const container = new TimestampedSortedContainer<string, number>();
205
container.addWithTimestamp("hello", 1);
206
container.addWithTimestamp("world", 2);
207
208
const timestamp = container.getTimestamp(1);
209
console.log(timestamp); // Date object
210
```
211
212
## Limitations and Considerations
213
214
### TypeScript Limitations
215
216
Generic class mixing exists because TypeScript doesn't allow generic parameters in base class expressions:
217
218
```typescript
219
// This is IMPOSSIBLE in TypeScript
220
class Mixed<T> extends Mixin(Generic1<T>, Generic2<T>) {} // ❌ Error
221
```
222
223
### Declaration Merging Requirement
224
225
You must always provide the interface declaration. Without it, TypeScript won't know about the mixed-in methods and properties:
226
227
```typescript
228
// Missing interface - TypeScript won't know about mixed methods
229
@mix(ClassA, ClassB) // ❌ Properties from ClassA, ClassB not available
230
class BadExample<T> {}
231
232
// Correct approach
233
interface GoodExample<T> extends ClassA<T>, ClassB<T> {}
234
@mix(ClassA, ClassB) // ✅ Properties available via interface
235
class GoodExample<T> {}
236
```
237
238
### Order Dependency
239
240
The order of classes in the `mix` decorator affects method override precedence, just like with regular `Mixin`.
241
242
### Constructor Limitations
243
244
The same constructor limitations apply as with regular mixing - constructors receive separate `this` contexts.