Lit-element: Document authoring mixins in TypeScript

Created on 9 Oct 2019  路  8Comments  路  Source: Polymer/lit-element

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:

  • I started by tweaking the code from model-viewer (which has mixins) 馃

    • I found this article useful to understand the Constructor type 馃敄

    • I stumbled into microsoft/TypeScript#7342 and Microsoft/TypeScript#30355 (as well as related PR at GoogleWebComponents/model-viewer#761) 馃

    • Finally, I found a working example at stackoverflow 馃檪

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?

docs Medium

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.

All 8 comments

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

Was this page helpful?
0 / 5 - 0 ratings

Related issues

chrismbeckett picture chrismbeckett  路  3Comments

quentin29200 picture quentin29200  路  3Comments

mercmobily picture mercmobily  路  3Comments

vdegenne picture vdegenne  路  4Comments

tamis-laan picture tamis-laan  路  4Comments