Update: simpler solution
You can forgo the extends clause in your mixin function and just use a generic T to represent your BehaviorSubject type:
export abstract class ComponentBase<T> {
model$ = new BehaviorSubject<T>(null);
}
export function genericMixin<T>(Base: AbstractConstructor<ComponentBase<T>>) {
abstract class GenericMixin extends Base {
//Logic here
}
return GenericMixin;
}
This works without having to do any casting. Your AppComponent class will also extend a little differently:
@Component({
selector: 'my-app',
templateUrl: './app.component.html',
styleUrls: ['./app.component.css'],
})
export class AppComponent extends genericMixin<Model>(ComponentBase) {
name = 'Angular';
method(): void {
this.model$.subscribe((v) => console.log(v.value));
}
}
But your model$ property should still infer correctly. Let me know if that works!
First solution
You can extract the type of your AbstractConstructor base class argument and explicitly annotate your return type from the mixin function, like so:
// An interface that represents what you're mixing in
interface WithModel<T> {
model$: BehaviorSubject<T>;
}
// Extracting the type of your `AbstractConstructor` parameter
type ExtractModelType<T extends AbstractConstructor<ComponentBase<any>>> =
T extends AbstractConstructor<ComponentBase<infer U>> ? U : never;
// Then in your mixin function
export function genericMixin<
T extends AbstractConstructor<ComponentBase<unknown>>
>(Base: T): T & WithModel<ExtractModelType<T>> {
abstract class GenericMixin extends Base {
// Logic here
}
return GenericMixin as T & WithModel<ExtractModelType<T>>;
}
Now accessing the model$ parameter from derived classes should yield the correct type.
class Model {
value: string;
}
@Component({
selector: 'my-app',
templateUrl: './app.component.html',
styleUrls: ['./app.component.css'],
})
export class AppComponent extends genericMixin(ComponentBase)<Model> {
name = 'Angular';
method(): void {
// `model$` is correctly inferred as a `Model` here.
this.model$.subscribe((v) => console.log(v.value));
}
}