I have been looking for a small example of how to create a mixin, but there wasn't one.
So far I was able to come up with the following snippet:
import { LitElement, property } from 'lit-element';
type Constructor<T = object> = {
new (...args: any[]): T;
prototype: T;
};
export interface ReadonlyInterface {
readonly: boolean;
}
export const ReadonlyMixin = <T extends Constructor<LitElement>>(
base: T
): T & Constructor<ReadonlyInterface> => {
class ReadonlyMixin extends base implements ReadonlyInterface {
@property({ type: Boolean }) readonly: boolean = false;
}
return ReadonlyMixin;
};
Some things that I learned before I was able to make it work:
Constructor type 馃敄 Overall it was a bit challenging to figure that out 馃槄 So the example would help a lot.
@justinfagnani @cdata can you please confirm if the code snippet above is correct? Also, do you have any other guidelines about how mixins should be written in TypeScript?
here's how I have been defining my mixing:
import { LitElement, property } from 'lit-element';
// or HTMLElement or other base class
type Constructor<T> = new (...args: any[]) => LitElement
interface ReadonlyInterface {
readonly: boolean;
}
type ReturnConstructor = new (...args: any[]) => LitElement & ReadonlyInterface
export function ReadonlyMixin<B extends Constructor> (Base: B): B & ReturnConstructor {
class Mixin extends Base implements ReadonlyInterface {
@property({ type: Boolean }) readonly: boolean = false;
}
return Mixin
}
Looks similar but I wonder if the differences are significant. 馃
TypeScript has added support for the mixin pattern. However, it does not work with the constructor type that is exported by LitElement (and which is also the one referenced in the first post of this issue).
The TS compatible constructor type is
export type Constructor<T = {}> = new (...args: any[]) => T;
If you use that compatible constructor, you don't need to specify an output type, as TypeScript will be able to derive it.
For an example of this pattern, see https://github.com/home-assistant/home-assistant-polymer/blob/dev/src/mixins/subscribe-mixin.ts
@justinfagnani, do you know why the constructor type of LitElement is the way it is?
I'm also interested on this! Right now I have:
type Constructor<T> = new (...args: any[]) => T;
export const myMixin = () => <T extends Constructor<HTMLElement>>(
baseElement: T
) => {
class MyMixin extends baseElement {
@property({ type: Object })
public data: any;
// ...
}
return MyMixin;
};
A follow-up question: how do we annotate protected methods defined on the mixin, in a way that would make them overridable by the class extending that mixin?
So far I only found the workaround with a dumb clas: https://github.com/microsoft/TypeScript/issues/25163#issuecomment-507074489
Here is the code example illustrating what I want to accomplish:
https://github.com/vaadin/component-mixins/pull/7/commits/852bce2da2f2f0b44b7220727e3a258bbf068f2a#diff-0838769eda184397106cb15d225e1bdbR8-R21
@justinfagnani @rictic I would really appreciate any advice on how to handle this case.
@balloob I'm interested to know if you are able to build with "declarations": true in your tsconfig.json.
For <model-viewer>, we briefly landed a change that moved from specifying a separate interface to relying on similar inference, but we ran into a wall because it would not compile if you configure the compiler to generate declaration files. See https://github.com/GoogleWebComponents/model-viewer/pull/761 for context.
@cdata correct, unable to build if we set declarations to true 馃槥. We're building an app and not a lib, so don't need declarations.
Regarding the above question, I submitted my research regarding available workarounds for TS mixins & protected methods to https://github.com/microsoft/TypeScript/issues/17744#issuecomment-558990381 so we can discuss it there.
It should be documented that if we use mixin classes, we must forgo the ability to publish them in a library with declaration files. https://github.com/microsoft/TypeScript/issues/35822
Most helpful comment
Regarding the above question, I submitted my research regarding available workarounds for TS mixins & protected methods to https://github.com/microsoft/TypeScript/issues/17744#issuecomment-558990381 so we can discuss it there.