In javascript you can do this:
function Test() {
this.id = 1;
};
Test.prototype.customize = function(key, callback) {
this[key] = callback;
};
var callback = function() { alert(this.id); };
var x = new Test();
x.customize('testing', callback);
x.testing();
Can you do a similar thing in typescript?
Particularly I'm interested in having a class like:
class Socket {
...
}
class Sockets {
public addChannel(name:string):void {
this[name] = new Socket();
}
...
}
data = new Sockets();
data.addChannel('video');
data.addChannel('audio');
...
var audio = data.audio.read();
var video = data.video.read();
etc.
The compiler complains that there's no 'audio' or 'video' member on 'Sockets', and won't compile. Is there a way to work around that without having to manually define the properties on the container class?
I know it kind of side steps the static typing rules, but I find it occasionally useful for API niceness to have something like this.
edit: See the example answer I posted below; something like that works. I'll still accept any clever answer that lets me somehow manage to compile something that does something useful on the base object itself.
Use an intersection type to extend a type in TypeScript, e.g. type TypeB = TypeA & {age: number;} . Intersection types are defined using an ampersand & and are used to combine existing object types. You can use the & operator as many times as necessary to construct a type.
Quick answer: JavaScript is a dynamically typed language, but TypeScript is a statically typed language. Longer answer: In dynamically typed languages all type checks are performed in a runtime, only when your program is executing.
Use an intersection type to add a property an an existing type in TypeScript, e.g. type Person = Employee & {myProperty: string} . Intersection types allow us to build up new types by extending them and are most commonly used to combine existing object types.
Interfaces are most recommended for defining new objects or methods or properties of an object where it will receive a specific component. Hence interface works better when using objects and method objects. Therefore it is our choice to choose between types or interface according to the program needs.
Update: It sounds like you are after truly dynamic behaviour at runtime... i.e. you don't know it will be video
, audio
or some other values. In this case, where you have a dynamic channel name from some source, you would have to use []
syntax - but there are still some
var channel = 'video;'
var data = new Sockets();
data.addChannel(channel);
// Totally dynamic / unchecked
var media = data[channel].read();
// Or
interface IChannel {
read(): { /* You could type this */ };
}
// Dynamic call, but read method and media variable typed and checked
var media = (<IChannel>data[channel]).read();
Previous answer... useful for anyone reading this question who isn't after full on dynamic behaviour (but pretty useless for Doug, sorry!)
If you want to extend an existing class, you can use inheritance:
class ClassOne {
addOne(input: number) {
return input + 1;
}
}
// ...
class ClassTwo extends ClassOne {
addTwo(input: number) {
return input + 2;
}
}
var classTwo = new ClassTwo();
var result = classTwo.addTwo(3); // 5
If you want to do this really dynamically, for example you just want to add something to an instance, you can do that too - but inheritance gives you much more for your money.
class ClassOne {
addOne(input: number) {
return input + 1;
}
}
// ...
var classOne = new ClassOne();
classOne['addTwo'] = function (input: number) {
return input + 2;
};
var result = (<any>classOne).addTwo(3); // 5
//or (nasty 'repeat the magic string version')
result = classOne['addTwo'](3);
If you are dead set on the dynamic route, a common pattern in TypeScript is to represent the structure with an interface, not a class. Interfaces (and modules, and enums) are open - so they can be extended over multiple blocks. You would need to ensure your interface and implementation were equally extended. This is the technique you use to extend built-in objects.
// NodeList is already declared in lib.d.ts - we are extending it
interface NodeList {
sayNodeList(): void;
}
NodeList.prototype.sayNodeList = function () {
alert('I say node, you say list... NODE');
}
This gives you full auto-completion and type checking.
You're not extending the type in your example... you're just dynamically adding a property to the object. In TypeScript you can't dynamically add a property to an object containing properties with different types--unless you cast to any
(which, in my opinion, should be avoided).
I would suggest adding a property to Sockets that is constrained to having properties who are of type Socket
like so:
class Sockets {
channels: { [index: string]: Socket; } = {};
public addChannel(name:string):void {
this.channels[name] = new Socket();
}
}
var data = new Sockets();
data.addChannel('audio');
// audio will be the return type of read... so you don't lose type
var audio = data.channels['audio'].read();
By doing this you do not have to remember to cast the object to Socket
and you get all the joys of type safety.
// Basic type
type a = {
prop1: string
}
// Extended type
type b = a & {
prop2: number
}
// Demo instance
let myExtendedObject: b = {
prop1: 'text',
prop2: 99
}
This is not possible yet but there is an ongoing and active discussion occurring right now among the Typescript community.
https://github.com/Microsoft/TypeScript/issues/9
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With