You signed in with another tab or window. Reload to refresh your session.You signed out in another tab or window. Reload to refresh your session.You switched accounts on another tab or window. Reload to refresh your session.Dismiss alert
{{ message }}
Repository navigation
Generating type definitions for mixin classes with protected members #17744
...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.
changed the title [-]Generating type definitions for mixins[/-][+]Generating type definitions for mixin classes with protected members[/+]on Aug 11, 2017
BTW I recently came up with an alternate way to represent mixins in TypeScript. Consider this example:
// Helper that copies properties from base's prototype// to child's prototypefunctionextend(child: any,base: any): void{for(varpropertyinbase){vardescriptor=Object.getOwnPropertyDescriptor(base,property)||{value: base[property]};Object.defineProperty(child,property,descriptor);}}// The mixin base classesclassBase1{constructor(){console.log('Base1.constructor() called')}publicmethod1(): void{console.log('method1() called')}}classBase2{constructor(){console.log('Base2.constructor() called')}publicmethod2(): void{console.log('method2() called')}}classBase3{constructor(){console.log('Base3.constructor() called')}private_prop3=123;publicgetprop3(): number{returnthis._prop3;}}// Collect the base classes into an intermediary "Mixin" classtypeMixin=Base1&Base2&Base3;constMixin: {new(): Mixin}=class{constructor(){Base1.call(this);Base2.call(this);Base3.call(this);}}asany;extend(Mixin.prototype,Base1.prototype);extend(Mixin.prototype,Base2.prototype);extend(Mixin.prototype,Base3.prototype);// Extend from the "Mixin" classclassChildextendsMixin{publicmethod4(): void{console.log('method4() called')}}constchild=newChild();child.method1();child.method2();console.log('prop3 = '+child.prop3);child.method4();
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:
The base classes don't need to be represented specially; they are ordinary TypeScript classes
You can use private members without any trouble
The generated .d.ts file is simple and understandable
Not sure I'd recommend to do this in real code, but it's interesting to contrast with the class-expression approach to mixins.
Reacted by Matthew Adams, Justin Fagnani, Zeno Jiricek, ForChange客服小弟, Andrey Godyaev, Mat Waroff, Nikita and Jayatubi
It bothers me too. Seems that this issue was moved into future milestone. No plan to solve it in the near future?
Well me too !
Ryan Cavanaugh (@RyanCavanaugh) Any update on when this could be fixed ? It's set on the "Future" backlog but as it's pretty huge, could you add some precision ?
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.
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:
// with dumb class: confusing<TextendsConstructor<SlottedItemsClass>>(base: T)// without dumb class: clean<TextendsConstructor<LitElement>>(base: T)
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
TS users would get protected methods listed in the code completion
Messing up public and protected API might need tweaks for tools
Summary
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.
Wesley Wigham (@weswigham) according to your comment at #17293 (comment), 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?
Reacted by Justin Fagnani, Pascal Schilp, Westbrook Johnson, Leho Kraav, Abdón Rodríguez, Andrew Noblet, Tomi Virkki, Yuriy Yevstihnyeyev, Florian Reuschel, Edu Martin and 7 more
Reacted by Abdón Rodríguez, Leho Kraav, Yuriy Yevstihnyeyev and Benny Powers
We hit this issue again when we tried to factor out a base class with private and protected members into a mixin.
Because we've already provided protected methods to subcasses of our base class, we really needed to keep existing protected members protected. If the members became public, then subclasses would get errors on trying to override public members with protected.
The workarounds we've had to do to satisfy the compiler are pretty onerous. We've created a fake class outside of the mixin and cast the mixin function to return an intersection of the argument and that class. Static require another fake class. Then we need to get our build system to remove the fake class. I'm not sure this approach solved every problem yet.
Well, we discussed it at our last design meeting as future work beyond abstract constructor types, however some team members think allowing a class-expression-like-thing in a type position (and having that refer to the static shape of the declared class) would be confusing. IMO, it makes sense since the shape stored by
type A = class {}
would be the same as the type of the const in
const A = class {}
But that's only my point of view. The disagreement within the team means we're going to need to see both a serious proposal and serious justification for why it's needed and how it makes sense before we're going to get close to agreement.
I come up with a (maybe) visually better solution based on The Dumb Class Approach.
Pros:
Easier to understand by hiding the ugly type details
Cons:
Still need to define two classes
Unsound: no guarantee that the mixin class implements the dumb class correctly
A little runtime overhead: uses a function createMixin to make TypeScript infer types
exporttypeConstructorOf<T>=new(...args: unknown[])=>TexporttypeMixinFactory<T,TBase>=(Base: ConstructorOf<TBase>)=>ConstructorOf<T>exportinterfaceMixinOf<T>{(): ConstructorOf<T><U={}>(Base?: ConstructorOf<U>): ConstructorOf<T&U>}exportfunctioncreateMixin<TBase={},T=TBase>(factory: MixinFactory<T,TBase>): MixinOf<T&TBase>{return((Base=Object)=>factory(Baseasany))asany}exporttypeMixinType<TextendsMixinOf<unknown>>=TextendsMixinOf<infer U> ? U : never// Case 1. define a normal mixin (with no type parameter)exporttypeFooMixin=MixinType<typeofFooMixin>exportconstFooMixin=createMixin(Base=>classFooMixinextendsBase{foo(){}})// Case 2. define a mixin with protected/private fields (with type parameter)declareclassBarMixinBase{protectedbar(): void}exporttypeBarMixin=MixinType<typeofBarMixin>exportconstBarMixin=createMixin<BarMixinBase>(Base=>classBarMixinextendsBase{protectedbar(){}})// UsageexportclassMyClassextendsFooMixin(BarMixin()){test(){this.foo()this.bar()}}
I recently encountered this issue when I tried to create mixins for WebComponents (more specifically with Lit). It's really annoying that the whole mixin situation still hasn't been fixed after 8 years, even though mixins are a well-known (and much needed) pattern.
Since the existing workarounds didn't fully meet my needs, I experimented with building on Jingkun Hua (@hjkcai)'s approach, with the following goals:
Support both standalone and constrained mixins
Enforce that a mixin class actually implements the dumb class
Allow constrained mixins to extend static properties
Ensure constrained mixins work with abstract and generic base classes
Allow mixins themselves to be abstract
The main drawback of my approach, forcing a mixin to actually implements the dumb class, is that you need two dumb classes. This means you have to define the properties three times in total – but you can usually auto-generate them after specifying them once (at least with VSCode).
Here's how it works:
The MixinAbstractAPI dumb class defines which properties and methods the mixin itself must implement.
The MixinAPI dumb class defines the shape of the class after the mixin is applied.
In most cases:
All members in MixinAbstractAPI should be marked as abstract.
The corresponding members in MixinAPI should not be abstract.
This ensures the mixin must implement those members, and the consuming class automatically gets them. Unfortunately, this is the only option I've found, as there is currently no way to have an interface with protected members or something similar. You can also skip the part with the second dumb class (MixinAbstractAPI), but then the implementation will not be enforced. (see example below)
Case 4: Constrained Mixin adding no new public/protected properties
exportconstWithLifecycleLoggingMixin=createConstrainedMixin<typeofLitElement>(Base=>classextendsBase{protectedoverridefirstUpdated(changedProperties: PropertyValues): void{super.firstUpdated(changedProperties);console.log(`${this.constructor.name} has been rendered to the DOM for the first time.`);}});
Case 5: Constrained Mixin adding new public/protected properties (no implementation guarantee)
exportclassButtonComponentextendsWithDisabledMixin(WithLifecycleLoggingMixin(WithLoggingMixin(WithIdMixin(LitElement)))){publicconstructor(){super();this.log(`Component created with ID: ${this.id}`);}publicclickHandler(): void{if(this.disabled){this.log('Button is disabled.');return;}this.log('Button clicked!');}}
Example: Abstract Mixin
declareabstractclassFormMixinAbstractAPI{protectedabstractsubmit(): void;// This property doesn't need to be implemented by the mixin.publicvalue: string;}exportdeclareabstractclassFormMixinAPIextendsFormMixinAbstractAPI{protectedsubmit(): void;// This property MUST be implemented by the class using the mixin.publicabstractvalue: string;}exportconstWithFormMixin=createMixin<FormMixinAPI,FormMixinAbstractAPI>(Base=>classextendsBase{publicoverridesubmit(): void{console.log('Submitting value:',this.value);}});exportclassFormComponentextendsWithFormMixin(LitElement){
@property()publicvalue='Hello World!';}
Example: Mixin with a Abstract Base Class
exportabstractclassSomeAbstractClass{publicabstractvalue: string;publicprintValue(): void{console.log(`Value from abstract base: ${this.value}`);}}exportclassSomeClassextendsWithIdMixin(SomeAbstractClass){publicvalue='Hello World!';}
Example: Mixin with a Generic Base Class
exportclassListComponent<T>extendsLitElement{
@property({type: Array})publicitems: T[]=[];}declareabstractclassSelectionMixinAbstractAPI{publicabstractselectedItem: number|null;publicabstractselectItem(index: number): void;}exportdeclareclassSelectionMixinAPIextendsSelectionMixinAbstractAPI{publicselectedItem: number|null;publicselectItem(item: number): void;}exportconstWithSelectionMixin=createConstrainedMixin<Constructor<LitElement>,SelectionMixinAPI,SelectionMixinAbstractAPI>(Base=>{classMixinextendsBase{
@property()publicselectedItem: number|null=null;publicselectItem(item: number): void{this.selectedItem=item;}}returnMixin;});exportinterfaceUser{id: number;name: string;}exportclassUserListSelectionComponentextendsWithSelectionMixin<typeofListComponent<User>>(ListComponent){// `this.items` has type `User[]`}
Activity
[-]Generating type definitions for mixins[/-][+]Generating type definitions for mixin classes with protected members[/+]4 remaining items
octogonz commentedon Oct 20, 2018
BTW I found that you can work around this limitation to some extent by using ECMAScript symbols, which provide a non-TypeScript way to hide a member:
IntelliSense will only show the
unsubscribemember to callers who have access to the unsubscribe symbol.hueyhe commentedon Mar 1, 2019
It bothers me too. Seems that this issue was moved into future milestone. No plan to solve it in the near future?
octogonz commentedon Mar 1, 2019
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.
Elvynia commentedon Aug 30, 2019
Well me too !
Ryan Cavanaugh (@RyanCavanaugh) Any update on when this could be fixed ? It's set on the "Future" backlog but as it's pretty huge, could you add some precision ?
web-padawan commentedon Nov 27, 2019
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
protectedmethods in the mixinsUsing
Symbolas recommended by ElixExample: see above comment Generating type definitions for mixin classes with protected members #17744 (comment)
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
interfaceExplicit 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
protectedmethods in the mixinsUsing
abstract class SomeMixinClassto "annotate" themExample: [ts] 'private' modifier cannot appear on a type member. #25163 (comment)
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
supercheck: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 theinterfaceJump to definition works as expected
Example: Make members private again hypermedia-app/Alcaeus#33
Pros
Cons
TS users would get protected methods listed in the code completion
Messing up
publicandprotectedAPI 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.
Wesley Wigham (@weswigham) according to your comment at #17293 (comment), 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?
weswigham commentedon Nov 27, 2019
Still needs a proposal.
trusktr commentedon May 12, 2020
Chris Frolik (@fr0) Also worth noting #35822
justinfagnani commentedon Nov 17, 2020
We hit this issue again when we tried to factor out a base class with private and protected members into a mixin.
Because we've already provided protected methods to subcasses of our base class, we really needed to keep existing protected members protected. If the members became public, then subclasses would get errors on trying to override public members with protected.
The workarounds we've had to do to satisfy the compiler are pretty onerous. We've created a fake class outside of the mixin and cast the mixin function to return an intersection of the argument and that class. Static require another fake class. Then we need to get our build system to remove the fake class. I'm not sure this approach solved every problem yet.
Wesley Wigham (@weswigham)
Is there anything anyone not on the TS team can do to help here?
weswigham commentedon Nov 18, 2020
Well, we discussed it at our last design meeting as future work beyond abstract constructor types, however some team members think allowing a class-expression-like-thing in a type position (and having that refer to the static shape of the declared class) would be confusing. IMO, it makes sense since the shape stored by
would be the same as the type of the
constinBut that's only my point of view. The disagreement within the team means we're going to need to see both a serious proposal and serious justification for why it's needed and how it makes sense before we're going to get close to agreement.
jogibear9988 commentedon Mar 19, 2021
Wesley Wigham (@weswigham) I think a list of why it is needed is in this issue: #35822
The list in the Description.
hjkcai commentedon Apr 1, 2021
I come up with a (maybe) visually better solution based on The Dumb Class Approach.
Pros:
Cons:
createMixinto make TypeScript infer typesHope the TS team to eventually solve the issue.
Enable declaration file emission. Fix issues that arrise from this
hampoelz commentedon Sep 29, 2025
I recently encountered this issue when I tried to create mixins for WebComponents (more specifically with Lit). It's really annoying that the whole mixin situation still hasn't been fixed after 8 years, even though mixins are a well-known (and much needed) pattern.
Since the existing workarounds didn't fully meet my needs, I experimented with building on Jingkun Hua (@hjkcai)'s approach, with the following goals:
The main drawback of my approach, forcing a mixin to actually implements the dumb class, is that you need two dumb classes. This means you have to define the properties three times in total – but you can usually auto-generate them after specifying them once (at least with VSCode).
Here's how it works:
MixinAbstractAPIdumb class defines which properties and methods the mixin itself must implement.MixinAPIdumb class defines the shape of the class after the mixin is applied.In most cases:
MixinAbstractAPIshould be marked as abstract.MixinAPIshould not be abstract.This ensures the mixin must implement those members, and the consuming class automatically gets them. Unfortunately, this is the only option I've found, as there is currently no way to have an interface with protected members or something similar. You can also skip the part with the second dumb class (
MixinAbstractAPI), but then the implementation will not be enforced. (see example below)Examples
Case 1: Standalone Mixin adding only public properties
Case 2: Standalone Mixin adding public/protected properties (no implementation guarantee)
Case 3: Standalone Mixin adding public/protected properties (with implementation guarantee)
Case 4: Constrained Mixin adding no new public/protected properties
Case 5: Constrained Mixin adding new public/protected properties (no implementation guarantee)
Case 6: Constrained Mixin adding new public/protected properties (with implementation guarantee)
Example: Composing multiple Mixins
Example: Abstract Mixin
Example: Mixin with a Abstract Base Class
Example: Mixin with a Generic Base Class
Example: Constrained Mixin extending static properties
Example: Constrained Mixin based on another Mixin
Example: Extending the EventMap of a Component (Generic Mixin)
Edit (2025-11-30): Fixes
TBasenot being enforced when using constrained mixins.