Studio Developer Manual / Version 2307
Table Of ContentsIn ECMAScript/TypeScript, a class can only extend one other class, but in TypeScript, it can implement multiple interfaces.
To understand how mixins work, it helps to know that in TypeScript, a class consists of its runtime JavaScript
value and a type, which is only relevant for type checking, that is
at compile-time.
The class identifier represents both aspects. Depending on context, it is clear whether the value, the type, or
both are meant. When a class A
extends another class B
, in the extends
clause, B
refers to both the value (JavaScript class A
will at runtime extend JavaScript
class B
) and the type (TypeScript type A
will at compile time be a subtype of type
B
). When using a class identifier behind a colon or in the implements
clause of a class,
only its type aspect it used. This allows to use a class in an implements
clause!
This equals implementing the interface extracted from that class.
Another TypeScript concept that is relevant here and closely related is declaration merging. In TypeScript, a type with the same identifier can be declared multiple times, and all declarations are merged. Since a class declares a value and a type, and an interface only declares a type, you cannot declare the same class twice, but you can declare a class and an interface using the same identifier. What happens is that the interface extracted from the class is merged with the additionally declared interface. This is how we tell TypeScript not to complain about the class not implementing the additional interface methods from the mixin. We call such an interface a companion interface of the class, as it comes together with the class and adds more declarations (the ones implemented in the mixin).
Using these ingredients, we can declare mixins in TypeScript as follows.
As in Ext JS, a mixin is a common TypeScript class. A mixin client class implements the interface automatically extracted from the mixin class, in other words, it directly implements the mixin class.
But that does not suffice: We have to specify that we do not only want to use the interface, but also want to mix
in the mixin's methods at runtime. You learned about the mixin()
utility function in the interface
chapter. Maybe now it becomes clear why it is called like that: it can do more than just mix in the identity of
an interface: it actually mixes in any class with all its members into the client class.
Last thing to do is again to prevent the type checker from complaining about missing implementations of the mixin
interface, since it does not know about the mixin magic. We declare a companion interface
of the mixin client class and let that extend the mixin class interface. We could even leave out the implements
clause of the mixin client class itself. However, to emphasize what's going on (and to help some IDEs that don't really
support declaration merging completely), we recommend specifying both clauses.
The following TypeScript code is an example of how an Ext mixin looks like in Ext TS.
// ./acme/MyMixin.ts class MyMixin { #mixinConfig: string = ""; get mixinConfig(): string { return this.#mixinConfig; } set mixinConfig(value: string) { this.#mixinConfig = value; } doSomething(): number { return this.#mixinConfig.length; } } export default MyMixin; // ./MixinClient.ts import { mixin } from "@jangaroo/runtime"; import Component from "@jangaroo/ext-ts/Component"; import MyMixin from "./acme/MyMixin"; class MixinClient extends Component implements MyMixin { constructor(config: any = null) { super(config); this.doSomething(); } } // companion interface, so we don't need to re-declare all mixin members: interface MixinClient extends MyMixin {} // use Jangaroo utility method to perform mixin operation: mixin(MixinClient, MyMixin); export default MixinClient;
Example 5.5. Ext Mixin in TypeScript example