Generating type definitions for mixin classes with protected members
See original GitHub issueTypeScript Version: 2.4.2
Code:
I’m using mixins as described by: https://github.com/Microsoft/TypeScript/pull/13743
export type Constructor<T> = new(...args: any[]) => T;
export function Unsubscriber<T extends Constructor<{}>>(Base: T) {
class Unsubscriber extends Base implements OnDestroy {
protected unsubscribe: Subject<void> = new Subject();
ngOnDestroy() {
this.unsubscribe.next();
this.unsubscribe.complete();
}
}
return Unsubscriber;
}
If I compile this code with "declaration": true
to get type definitions for my library, I get the following error:
ERROR in (truncated)/mixins.ts (...): Return type of exported function has or is using private name 'Unsubscriber'.
One solution is to add an interface…
export interface IUnsubscriber extends OnDestroy {
unsubscribe: Subject<void>;
}
…and have my mixin function
have a return type of Constructor<IUnsubscriber>
. This works, but it forces me to make the properties/methods exposed by my mixin be public
even in cases where I want them to be protected
.
Short of adding protected
members to interfaces (which I’m not sure is the right thing to do), this seems to be a limitation of the currently supported mixin strategy.
Issue Analytics
- State:
- Created 6 years ago
- Reactions:46
- Comments:11 (2 by maintainers)
Top Results From Across the Web
Documentation - Mixins - TypeScript
The pattern relies on using generics with class inheritance to extend a base class. TypeScript's best mixin support is done via the class...
Read more >Typescript mixin with protected access - Stack Overflow
A mixin called Activatable that needs access to the protected members of User . type Constructor<T = {}> = new (... args: any[])...
Read more >The Mixin Pattern In TypeScript – All You Need To Know Part 2
Mixin class methods then can refer to the generic type as this[ 'Element' ] . The generic arguments of the outer and nested...
Read more >How to Handle Multiple Inheritance and TypeScript Mixins
Multiple inheritance is a feature where a class can extend more than one base class. The problem. What if we want to create...
Read more >Mixin Classes in TypeScript - Marius Schulz
A mixin class is a class that implements a distinct aspect of functionality. Other classes can then include the mixin and access its...
Read more >
Top Related Medium Post
No results found
Top Related StackOverflow Question
No results found
Troubleshoot Live Code
Lightrun enables developers to add logs, metrics and snapshots to live code - no restarts or redeploys required.
Start Free
Top Related Reddit Thread
No results found
Top Related Hackernoon Post
No results found
Top Related Tweet
No results found
Top Related Dev.to Post
No results found
Top Related Hashnode Post
No results found
Here is my research regarding possible workarounds for TS mixins with protected methods.
Overview
These are 3 approaches that can be used for writing mixins that need to expose protected methods. They all allow subclasses extending such mixins to call these methods using
super
.1. Symbol approach
Not using
protected
methods in the mixinsUsing
Symbol
as recommended by ElixExample: see above comment https://github.com/microsoft/TypeScript/issues/17744#issuecomment-431534647
Pros
Methods are truly protected (can’t be called from outside, e.g. by third party code)
Methods are “public” in terms of TS and can be annotated on the
interface
Explicit exports potentially help with refactoring of the consumers’ subclasses
Methods are excluded from the DevTools console’s auto-complete list (good for user)
Avoid potential name conflicts if user extends a class with their own methods
Cons
Consumers have to import symbols if they need to override protected methods
Jump to definition is not ideal (jumps to the place where
Symbol()
is created)2. Dumb class approach
Using
protected
methods in the mixinsUsing
abstract class SomeMixinClass
to “annotate” themExample: https://github.com/microsoft/TypeScript/issues/25163#issuecomment-507074489
Pros
No need to import anything (except the mixin itself) for the consumer component
Methods are easy to access in the DevTools console (good for maintainer)
Cons
Both interface (for public API) and abstract class (for protected API) are needed
The abstract class ends up in the JS output (as empty class with no methods)
The mixin function needs to confusingly accept the argument with a type cast (instead of the actual expected class) in order to pick up the annotated protected methods from it:
Need for extra
super
check:super._focus && super._focus()
- this is needed because the abstract classes mark methods using_focus?()
, i.e. “possibly undefined”Jump to definition from the subclass goes to a dumb class
3. Public methods
Keep methods
public
, add them on theinterface
Jump to definition works as expected
Example: https://github.com/wikibus/Alcaeus/issues/33
Pros
Cons
TS users would get protected methods listed in the code completion
Messing up
public
andprotected
API might need tweaks for toolsSummary
Personally, I like symbols approach more because it’s cleaner and bulletproof. The “jump to definition” inconvenience is a drawback that is more of a personal preference.
On the other hand, the dumb class approach has its benefits: once the issue is resolved, we can get rid of those dumb classes and potentially keep the methods themselves unchanged.
@weswigham according to your comment at https://github.com/microsoft/TypeScript/issues/17293#issuecomment-522491710, what do you have on your mind to tackle these issues? Is there a plan, or does this still need a concrete proposal that you mentioned?
BTW I recently came up with an alternate way to represent mixins in TypeScript. Consider this example:
The behavior is probably unsound if the base classes happen to have members with the same name. But this issue could be detected in the
extend()
function.There are some interesting advantages:
Not sure I’d recommend to do this in real code, but it’s interesting to contrast with the class-expression approach to mixins.