Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Document authoring mixins in TypeScript #829

Closed
web-padawan opened this issue Oct 9, 2019 · 9 comments
Closed

Document authoring mixins in TypeScript #829

web-padawan opened this issue Oct 9, 2019 · 9 comments

Comments

@web-padawan
Copy link
Contributor

web-padawan commented Oct 9, 2019

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:

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?

@tpluscode
Copy link

tpluscode commented Oct 9, 2019

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. 🤔

@balloob
Copy link
Contributor

balloob commented Oct 28, 2019

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?

@abdonrd
Copy link
Contributor

abdonrd commented Oct 29, 2019

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;
};

@web-padawan
Copy link
Contributor Author

web-padawan commented Nov 20, 2019

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: microsoft/TypeScript#25163 (comment)

Here is the code example illustrating what I want to accomplish:
vaadin/component-mixins@852bce2#diff-0838769eda184397106cb15d225e1bdbR8-R21

@justinfagnani @rictic I would really appreciate any advice on how to handle this case.

@cdata
Copy link

cdata commented Nov 22, 2019

@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 google/model-viewer#761 for context.

@balloob
Copy link
Contributor

balloob commented Nov 22, 2019

@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.

@web-padawan
Copy link
Contributor Author

web-padawan commented Nov 27, 2019

Regarding the above question, I submitted my research regarding available workarounds for TS mixins & protected methods to microsoft/TypeScript#17744 (comment) so we can discuss it there.

@trusktr
Copy link

trusktr commented Dec 21, 2019

It should be documented that if we use mixin classes, we must forgo the ability to publish them in a library with declaration files. microsoft/TypeScript#35822

@web-padawan
Copy link
Contributor Author

The new docs website has a dedicated page:
https://lit.dev/docs/composition/mixins/#mixins-in-typescript

Closing as fixed.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Projects
None yet
Development

No branches or pull requests

7 participants