Typescript pass generic to Map - javascript

I want to pass a generic type from the child class to a property.
interface MyInterface {
method(): void;
}
class B<P> {
entities = new Map<number, P>();
}
class C<P = MyInterface> extends B<P> {
click() {
this.entities.get(1).method();
}
}
I'm expecting each entity to be of type MyInterface, but I'm getting a type error:
Property method() doesn't exist on type P
What is the issue?

Just because P = MyInterface does not mean any passed in P must extend MyInterface. For example with no other constraints, this would also be valid:
new C<{ noMethod() : void }>();
You need to add a constraint to P to say that any passed in P must extend MyInterface, and you can keep MyInterface as a default as well.
interface MyInterface {
method(): void;
}
class B<P> {
entities = new Map<number, P>();
}
class C<P extends MyInterface = MyInterface> extends B<P> {
click() {
this.entities.get(1).method();
}
}

Related

TypeScript - declare type of a React component class constructor with a certain static method

I got this to work with regular classes, including classes that extend the target class as you can see below:
interface ALike {
a: () => boolean
}
interface ALikeConstructor {
new (): ALike
aStatic: () => boolean
}
function acceptsALike(ctr: ALikeConstructor) { }
class A {
public a() {
return true;
}
public static aStatic(): boolean {
return true;
}
}
class AB extends A {
public b() {
return true;
}
}
class ANoStatic {
public a() {
return true;
}
}
acceptsALike(A); // ok
acceptsALike(AB); // ok
acceptsALike(ANoStatic); // ok (error shows)
Playground
However, for whatever reason it does not work with React components and TypeScript does not give much of an explanation.
import React from 'react'
type Props = {
test: true
}
interface CLike extends React.Component<Props> {
c(): boolean
}
interface CLikeConstructor {
new(): CLike
cStatic(): boolean
}
class C extends React.Component<Props> implements CLike {
public c() {
return true;
}
public static cStatic() {
return true;
}
}
function acceptsCLike(ctr: CLikeConstructor) { }
acceptsCLike(C); // Argument of type 'typeof C' is not assingable to parameter of type 'CLikeConstructor' - no further explanation.
Playground
I understand that something about React.Component makes it incompatible, but what is it and is there a way to work around it?
The problem is that your constructor signature (in CLikeConstructor) describes a constructor with no arguments. The react class has a constructor with one argument (the props).
You can defined the constructor signature to not care about the number of args:
interface CLikeConstructor {
new(...a: any[]): CLike
cStatic(): boolean
}
Playground Link
You could also make it accept just one param (the props) like this: new(props: any): CLike

How to extends abstract class from template in TypeScript?

I have an architecture like that and I want to extends my TemplateService with a template but actually I have an error on it. Is there a way to extends an abstract class from a template ?
// Error message
ReferenceError: Service is not defined
abstract class TemplateService<Service> extends Service
^
// My code
abstract class TemplateService<Service> extends Service { // Cannot find name 'Service'.
constructor() {
const name = "Hello world !";
super(name);
}
public getJob(): string {
return "Job";
}
protected getAge(): number {
return 25;
}
}
class ImportedService {
constructor(private _name: string) {}
public getName() {
return this._name;
}
}
class MyService extends TemplateService<ImportedService> {
constructor() {
super();
}
public fooMethod(): void {
...
}
}
It is a category error to write
abstract class TemplateService<Service> extends Service {}
TypeScript's static type system, including generic type parameters, is erased from the emitted JavaScript that actually runs. In the above, abstract and <Service> are part of the static type system, while the rest is valid JavaScript (at least for modern versions of JavaScript). Your code as written would be emitted to something like
class TemplateService extends Service {}
which hopefully shows the problem. Unless there happens to be a class constructor named Service in scope, this would be a runtime error. You can't subclass a type; you need to subclass a constructor value.
TypeScript has some support for subclassing an arbitrary constructor value via the concept of mixins. Instead of writing TemplateService as a class, you make it a class factory function that accepts a class constructor as an input. Conceptually it would look like
// don't write this, it's not valid TS
function TemplateService<C extends new (name: string) => object>(ctor: C) {
return class extends ctor { // error!
// ------> ~~~~~
constructor() {
const name = "Hello world !";
super(name);
}
public getJob(): string {
return "Job";
}
protected getAge(): number {
return 25;
}
};
}
And you'd use it like
class ImportedService {
constructor(private _name: string) { }
public getName() {
return this._name;
}
}
class MyService extends TemplateService(ImportedService) {
constructor() {
super();
}
public fooMethod(): void {
console.log(this.getName());
console.log(this.getJob());
console.log(this.getAge())
}
}
Unfortunately, TypeScript's mixin support assumes that you are going to just pass the constructor parameters through, so the above TemplateService implementation gives compiler errors. There are open issues about this, such as microsoft/TypeScript#37142. For now all I could say is to work around it by using several type assertions to trick the compiler into allowing the mixin and into to giving it the correct type.
For the particular example code here, it could look like:
function TemplateService<C extends new (name: string) => object>(ctor: C) {
const c = class extends (ctor as new (...args: any) => object) {
constructor() {
const name = "Hello world !";
super(name);
}
public getJob(): string {
return "Job";
}
protected getAge(): number {
return 25;
}
};
return c as {
new(): InstanceType<C> & InstanceType<typeof c>;
}
}
Now the compiler believes that TemplateService(ImportedService) is a no-arg constructor which produces instances of a combination (via intersection) of ImportedService and the type of the anonymous class expression with a getJob() method and protected getAge() method.
Playground link to code

How to describe an index type with generic values in Typescript?

Is there any way to describe this interface in typescript:
abstract class Entity { /*...*/ }
interface List<A extends Entity> { /*...*/ }
// I want the below interface
interface Collections {
[name: string]: List<A extends Entity>;
}
The Collections object contains different List objects of classes that extend the Entity class. So the following code doesn't solve my problem:
interface Collections<A extends Entity> {
[name: string]: List<A>;
}
The above interface serves only for one single class A, I want it to serve multiple classes extend from Entity class.
Thank you!
If I caught the idea, this might be the solution?
abstract class Entity { /*...*/ }
class Book extends Entity { title = 'default'; }
class Spoon extends Entity { price = 5; }
interface List<A> { /*...*/ }
interface EntityList extends List<Entity>{}
interface Collections {
[name: string]: EntityList
}
const t: Collections = {
'test': [new Book(), new Spoon()]
}
const x = t['test'];
If you don't wont to close type you might do this:
interface Collections<T> {
[name: string]: List<T>
}
const t: Collections<Entity> = {
'test': [new Book(), new Spoon()]
}

How to pass a string or class to a method to create instance

I am to use the following method, it works by passing a type to it such as obj.addComponent(MyClass). This works just fine.
I tried to modify the type parameter by adding | string to it, but it now gives me errors saying:
Cannot use 'new' with an expression whose type lacks a call or construct signature.
Is there anyway for me to modify this so that I can pass either a Class name in or a string version of the class?
Here is what I have that doesn't work:
public addComponent<T extends Component>(type: ComponentType<T> | string): T {
let comp;
comp = new type() as T;
comp.name = comp.constructor.name;
}
Here are its dependencies:
class Component extends Obj {
}
interface ComponentType<T extends Component> {
new(): T;
}
I have tried using Object.create(), which works fine, but then I get a new error:
Uncaught TypeError: Cannot assign to read only property 'name' of object '[object Object]'
Edit:
In the end I would like to be able to pass the following to addComponent:
obj.addComponent(MyClass);
Or
obj.addComponent("MyClass");
There's no way to get the class using a name in javascript, it doesn't have something similar to the java ClassLoader.
You can get around that by creating your own mechanism, and there are probably many ways to do so, but here are 3 options.
(1) Maintain a registry for your component classes:
const REGISTRY: { [name: string]: ComponentType<Component> } = {};
class Component {}
class MyComponent1 extends Component {}
REGISTRY["MyComponent1"] = MyComponent1;
class MyComponent2 extends Component {}
REGISTRY["MyComponent2"] = MyComponent2;
type ComponentType<T extends Component> = {
new(): T;
}
function factory<T extends Component>(type: ComponentType<T> | string): T {
return typeof type === "string" ?
new REGISTRY[type]() as T:
new type();
}
(code in playground)
If you go with this approach then I suggest to make the REGISTRY an object that holds the collection, that way you can add the ctor only and get the name from that.
There's a variant for this and that's to use a decorator:
function register(constructor: typeof Component) {
REGISTRY[(constructor as any).name] = constructor;
}
#register
class MyComponent1 extends Component {}
#register
class MyComponent2 extends Component {}
(code in playground)
(2) Wrap the components in a namespace (As #Shilly suggested in a comment):
namespace components {
export class Component {}
export class MyComponent1 extends Component {}
export class MyComponent2 extends Component {}
export type ComponentType<T extends Component> = {
new(): T;
}
export function forName(name: string): ComponentType<Component> {
if (this[name] && this[name].prototype instanceof Component) {
return this[name];
}
}
}
function factory<T extends components.Component>(type: components.ComponentType<T> | string): T {
return typeof type === "string" ?
new (components.forName(type))() as T:
new type();
}
(code in playground)
If you're going with this approach then you need to make sure that all the component classes are exported.
(3) Use eval
class Component {}
class MyComponent1 extends Component {}
class MyComponent2 extends Component {}
type ComponentType<T extends Component> = {
new(): T;
}
function factory<T extends Component>(type: ComponentType<T> | string): T {
return typeof type === "string" ?
new (eval(type))() as T:
new type();
}
(code in playground)
This isn't a recommended approach, and you can read all about the cons in using eval in a lot of places.
But it's still an option so I'm listing it.
There is a way to instantiate classes by their name as String if they are in a namespace :
var variableName: any = new YourNamespace[YourClassNameString](ClassParameters);
For exmaple, this should work :
namespace myNamespace {
export class myClass {
example() {
return true;
}
}
}
var newClass: any = new myNamespace["myClass"](); // <- This loads the class A.
newClass.example();
This will instantiate the class myClass using the string "myClass".
Thus, to come back to your situation, I think this will work :
namespace myNamespace {
// The dependencies you defined
export class Component {
}
export interface ComponentType<T extends Component> {
new(): T;
}
// Just a class to contain the method's code
export class Example {
public addComponent<T extends Component>(type: ComponentType<T> | string): T {
let result: T;
if (typeof type === "string") {
result = new myNamespace[type]();
} else {
result = new type();
}
return result;
}
}
}
Then, you'll be able to do this :
let stringToLoad = "Component";
let classToLoad = Component;
let example = new Example();
let result1: Component = example.addComponent(stringToLoad);
let result2: Component = example.addComponent(classToLoad);
Playground version with code + test : here

Typescript: How to extend two classes?

I want to save my time and reuse common code across classes that extend PIXI classes (a 2d webGl renderer library).
Object Interfaces:
module Game.Core {
export interface IObject {}
export interface IManagedObject extends IObject{
getKeyInManager(key: string): string;
setKeyInManager(key: string): IObject;
}
}
My issue is that the code inside getKeyInManager and setKeyInManager will not change and I want to reuse it, not to duplicate it, here is the implementation:
export class ObjectThatShouldAlsoBeExtended{
private _keyInManager: string;
public getKeyInManager(key: string): string{
return this._keyInManager;
}
public setKeyInManager(key: string): DisplayObject{
this._keyInManager = key;
return this;
}
}
What I want to do is to automatically add, through a Manager.add(), the key used in the manager to reference the object inside the object itself in its property _keyInManager.
So, let's take an example with a Texture. Here goes the TextureManager
module Game.Managers {
export class TextureManager extends Game.Managers.Manager {
public createFromLocalImage(name: string, relativePath: string): Game.Core.Texture{
return this.add(name, Game.Core.Texture.fromImage("/" + relativePath)).get(name);
}
}
}
When I do this.add(), I want the Game.Managers.Manager add() method to call a method which would exist on the object returned by Game.Core.Texture.fromImage("/" + relativePath). This object, in this case would be a Texture:
module Game.Core {
// I must extend PIXI.Texture, but I need to inject the methods in IManagedObject.
export class Texture extends PIXI.Texture {
}
}
I know that IManagedObject is an interface and cannot contain implementation, but I don't know what to write to inject the class ObjectThatShouldAlsoBeExtended inside my Texture class. Knowing that the same process would be required for Sprite, TilingSprite, Layer and more.
I need experienced TypeScript feedback/advice here, it must be possible to do it, but not by multiple extends since only one is possible at the time, I didn't find any other solution.
There is a little known feature in TypeScript that allows you to use Mixins to create re-usable small objects. You can compose these into larger objects using multiple inheritance (multiple inheritance is not allowed for classes, but it is allowed for mixins - which are like interfaces with an associated implenentation).
More information on TypeScript Mixins
I think you could use this technique to share common components between many classes in your game and to re-use many of these components from a single class in your game:
Here is a quick Mixins demo... first, the flavours that you want to mix:
class CanEat {
public eat() {
alert('Munch Munch.');
}
}
class CanSleep {
sleep() {
alert('Zzzzzzz.');
}
}
Then the magic method for Mixin creation (you only need this once somewhere in your program...)
function applyMixins(derivedCtor: any, baseCtors: any[]) {
baseCtors.forEach(baseCtor => {
Object.getOwnPropertyNames(baseCtor.prototype).forEach(name => {
if (name !== 'constructor') {
derivedCtor.prototype[name] = baseCtor.prototype[name];
}
});
});
}
And then you can create classes with multiple inheritance from mixin flavours:
class Being implements CanEat, CanSleep {
eat: () => void;
sleep: () => void;
}
applyMixins (Being, [CanEat, CanSleep]);
Note that there is no actual implementation in this class - just enough to make it pass the requirements of the "interfaces". But when we use this class - it all works.
var being = new Being();
// Zzzzzzz...
being.sleep();
I would suggest using the new mixins approach described there: https://blogs.msdn.microsoft.com/typescript/2017/02/22/announcing-typescript-2-2/
This approach is better, than the "applyMixins" approach described by Fenton, because the autocompiler would help you and show all the methods / properties from the both base and 2nd inheritance classes.
This approach might be checked on the TS Playground site.
Here is the implementation:
class MainClass {
testMainClass() {
alert("testMainClass");
}
}
const addSecondInheritance = (BaseClass: { new(...args) }) => {
return class extends BaseClass {
testSecondInheritance() {
alert("testSecondInheritance");
}
}
}
// Prepare the new class, which "inherits" 2 classes (MainClass and the cass declared in the addSecondInheritance method)
const SecondInheritanceClass = addSecondInheritance(MainClass);
// Create object from the new prepared class
const secondInheritanceObj = new SecondInheritanceClass();
secondInheritanceObj.testMainClass();
secondInheritanceObj.testSecondInheritance();
I found an up-to-date & unparalleled solution: https://www.npmjs.com/package/ts-mixer
You are welcome :)
TypeScript supports decorators, and using that feature plus a little library called typescript-mix you can use mixins to have multiple inheritance with just a couple of lines
// The following line is only for intellisense to work
interface Shopperholic extends Buyer, Transportable {}
class Shopperholic {
// The following line is where we "extend" from other 2 classes
#use( Buyer, Transportable ) this
price = 2000;
}
I think there is a much better approach, that allows for solid type-safety and scalability.
First declare interfaces that you want to implement on your target class:
interface IBar {
doBarThings(): void;
}
interface IBazz {
doBazzThings(): void;
}
class Foo implements IBar, IBazz {}
Now we have to add the implementation to the Foo class. We can use class mixins that also implements these interfaces:
class Base {}
type Constructor<I = Base> = new (...args: any[]) => I;
function Bar<T extends Constructor>(constructor: T = Base as any) {
return class extends constructor implements IBar {
public doBarThings() {
console.log("Do bar!");
}
};
}
function Bazz<T extends Constructor>(constructor: T = Base as any) {
return class extends constructor implements IBazz {
public doBazzThings() {
console.log("Do bazz!");
}
};
}
Extend the Foo class with the class mixins:
class Foo extends Bar(Bazz()) implements IBar, IBazz {
public doBarThings() {
super.doBarThings();
console.log("Override mixin");
}
}
const foo = new Foo();
foo.doBazzThings(); // Do bazz!
foo.doBarThings(); // Do bar! // Override mixin
Unfortunately typescript does not support multiple inheritance. Therefore there is no completely trivial answer, you will probably have to restructure your program
Here are a few suggestions:
If this additional class contains behaviour that many of your subclasses share, it makes sense to insert it into the class hierarchy, somewhere at the top. Maybe you could derive the common superclass of Sprite, Texture, Layer, ... from this class ? This would be a good choice, if you can find a good spot in the type hirarchy. But I would not recommend to just insert this class at a random point. Inheritance expresses an "Is a - relationship" e.g. a dog is an animal, a texture is an instance of this class. You would have to ask yourself, if this really models the relationship between the objects in your code. A logical inheritance tree is very valuable
If the additional class does not fit logically into the type hierarchy, you could use aggregation. That means that you add an instance variable of the type of this class to a common superclass of Sprite, Texture, Layer, ... Then you can access the variable with its getter/setter in all subclasses. This models a "Has a - relationship".
You could also convert your class into an interface. Then you could extend the interface with all your classes but would have to implement the methods correctly in each class. This means some code redundancy but in this case not much.
You have to decide for yourself which approach you like best. Personally I would recommend to convert the class to an interface.
One tip: Typescript offers properties, which are syntactic sugar for getters and setters. You might want to take a look at this: http://blogs.microsoft.co.il/gilf/2013/01/22/creating-properties-in-typescript/
A very hacky solution would be to loop through the class you want to inherit from adding the functions one by one to the new parent class
class ChildA {
public static x = 5
}
class ChildB {
public static y = 6
}
class Parent {}
for (const property in ChildA) {
Parent[property] = ChildA[property]
}
for (const property in ChildB) {
Parent[property] = ChildB[property]
}
Parent.x
// 5
Parent.y
// 6
All properties of ChildA and ChildB can now be accessed from the Parent class, however they will not be recognised meaning that you will receive warnings such as Property 'x' does not exist on 'typeof Parent'
In design patterns there is a principle called "favouring composition over inheritance". It says instead of inheriting Class B from Class A ,put an instance of class A inside class B as a property and then you can use functionalities of class A inside class B.
You can see some examples of that here and here.
In my case I used concatenative inheritance.
Maybe for someone this way will be helpful:
class Sprite {
x: number;
y: number;
constructor(x: number, y: number) {
this.x = x;
this.y = y;
}
}
class Plane extends Sprite {
fly(): string {
return 'I can Fly!'
}
}
class Enemy {
isEnemy = true;
}
class Player {
isPlayer = true;
}
// You can create factory functions to create new instances
const enemyPlane = Object.assign(new Plane(1, 1), new Enemy());
const playerPlane = Object.assign(new Plane(2, 2), new Player());
Also I recommend reading Eric Elliott's articles about js inheritance:
The Heart & Soul of Prototypal OO: Concatenative Inheritance
3 Different Kinds of Prototypal Inheritance
There are so many good answers here already, but i just want to show with an example that you can add additional functionality to the class being extended;
function applyMixins(derivedCtor: any, baseCtors: any[]) {
baseCtors.forEach(baseCtor => {
Object.getOwnPropertyNames(baseCtor.prototype).forEach(name => {
if (name !== 'constructor') {
derivedCtor.prototype[name] = baseCtor.prototype[name];
}
});
});
}
class Class1 {
doWork() {
console.log('Working');
}
}
class Class2 {
sleep() {
console.log('Sleeping');
}
}
class FatClass implements Class1, Class2 {
doWork: () => void = () => { };
sleep: () => void = () => { };
x: number = 23;
private _z: number = 80;
get z(): number {
return this._z;
}
set z(newZ) {
this._z = newZ;
}
saySomething(y: string) {
console.log(`Just saying ${y}...`);
}
}
applyMixins(FatClass, [Class1, Class2]);
let fatClass = new FatClass();
fatClass.doWork();
fatClass.saySomething("nothing");
console.log(fatClass.x);
You can call Dynamic Inheritance or Class Factory.
type ClassConstructor<T> = {
new (...args: any[]): T;
};
interface IA {
a: string;
}
interface IB {
b: string;
}
interface IAB extends IA, IB {}
class EmptyClass {}
function GetA<T>(t: ClassConstructor<T> = EmptyClass as any) {
class A extends (t as any) implements IA {
a = 'Default value a';
}
return A as unknown as ClassConstructor<IA & T>;
}
function GetB<T>(t: ClassConstructor<T> = EmptyClass as any) {
class B extends (t as any) implements IB {
b = 'Default value b';
}
return B as unknown as ClassConstructor<IB & T>;
}
class C extends GetA<IB>(GetB()) implements IAB {}
Found a way:
export interface IsA {
aWork(): void
}
export interface IsB {
}
export class A implements IsA {
aWork() { }
}
export class B implements IsB {
bWork() { }
}
export interface IsAB extends A, B {}
export class IsAB {}
Then you can
export class C extends IsAB {}
or even
const c = new IsAB {}

Categories

Resources