CoreMedia Content Cloud v11 Upgrade Guide / Version 2110
Table Of ContentsIn TypeScript, we use a quite similar approach like in ActionScript, but fortunately, the syntax is much more elegant.
To understand how mixins work, it helps to know that in TypeScript, a class consists of a it's run-time JavaScript
value and a type, which is only relevant for type checking / 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 run-time extend JavaScript
class B
) and the type (TypeScript type A
will at compile-time be a sub-type 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. In this case, TypeScript
does not complain about the class not implementing the additional interface methods. We call
such an interface a companion interface of the class, as it comes together with the class
and adds more declarations (the ones we had to declare as native
in ActionScript).
Using these ingredients, we can declare mixins in TypeScript as follows.
As in Ext JS, a mixin is a usual 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 run-time. We 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. This is much more elegant in TypeScript than in ActionScript:
Instead of re-declaring every single member using the native
keyword, we just 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, but to emphasize what's going on (and to help some IDEs that don't really
support declaration merging completely), during conversion, we generate both clauses.
All in all, the above example results in the following quite more compact TypeScript code.
// ./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 7.4. Mixins in TypeScript example