First of all, I'm very new to TS, and during my reading in
http://www.typescriptlang.org/Handbook I stop at trying to understand how Hybrid Types Interface works.
In the example of the TS:
interface ICounter {
(start: number): string;
interval: number;
reset(): void;
}
var c: ICounter;
c(10);
c.reset();
c.interval = 5.0;
So, the problem was when I tried to write a class using this interface, the problem was the line:
(start: number): string;
First I thought that this line represents a function so tried to create:
class Test implements ICounter {
interval: number;
reset(): void { }
start(start: number): string {
return "";
}
}
But this keep showing that "Types Test and ICounter have incompatible signatures", so what I'm missing here? I thought that interfaces should work the same way for classes and variables.
ICounter represents a function that has two properties—interval and reset.
This line in the interface...
(start: number): string;
...describes a way to call the function. It's shown in the example as:
c(10)
The other lines describe the function's properties—interval and reset.
c.reset();
c.interval = 5.0;
As stated in the handbook, this is used for representing JavaScript libraries that do this. So for example, the above could represent the following JavaScript code:
function counter(start) {
alert(start);
return "some string";
}
counter.reset = function() { alert('reset called'); };
counter.interval = 1;
In addition to all this, note that the class you described could be represented by the following interface:
interface ITest {
interval: number;
reset: () => void;
start: (start: number) => string;
}
class implies that you will create instances via new operator. new creates object, not function, so it is impossible to implement class, which instances can called as function. You can do something like this:
interface ICounterObject {
interval: number;
reset(): void;
}
interface ICounter extends ICounterObject {
(start: number): string;
__proto__: ICounterObject;
}
class Test implements ICounterObject {
static createCounter(): ICounter {
var counter = <ICounter>function (start: number): string {
return "";
};
counter.__proto__ = Test.prototype;
return counter;
}
interval: number;
reset(): void { }
}
Note, __proto__ is from ES6, but de-facto is supported in ES5 browsers. If you want to use function prototype members (e.g call and apply) mix prototypes:
function mixWithFunc<T extends Function>(obj: { __proto__?}, func: T) {
var objProto = <{ constructor }>obj.__proto__;
var objClass = <{ __mixedProto__ }>objProto.constructor;
var proto = <typeof obj>objClass.__mixedProto__;
if (!proto) {
proto = {};
proto.__proto__ = objProto;
['call', 'apply', 'bind'].forEach(p => proto[p] = Function.prototype[p]);
objClass.__mixedProto__ = proto;
}
(<typeof obj>func).__proto__ = proto;
Object.getOwnPropertyNames(obj).forEach(p => func[p] = obj[p]);
return func;
}
interface ICounter extends Counter {
(start: number): string;
}
class Counter {
static create() {
var self: ICounter = mixWithFunc(
new Counter(),
<ICounter>function (start: number) {
return "started with interval: " + self.interval;
});
return self;
}
interval = 1000;
reset() { this.interval = 0; }
}
Of course you can just add members to each instance of your function instead of prototyping.
Related
I have declared a class Month in my typescript project. It has 6 properties and 7 member methods, 4 of which are private.
When trying to instantiate an instance of Month via the following code, I'm getting an error saying that I didn't provide a method as argument to the parameter. this.hasAdditionalCategories is class method. Why do I need to provide it? My understanding was that I need to provide the properties only to instantiate, and then the instance would inherit the methods from the class definition.
Does this seem plausible? Cheers
The error message is
[ts] Argument of type '{ date: Date; comment: null; expectedCashFlow: number; hedgeRatio: number; expectedCashFlowHedge:...' is not assignable to parameter of type 'Month'.
Property 'hasAdditionalCategories' is missing in type '{ date: Date; comment: null; expectedCashFlow: number; hedgeRatio: number; expectedCashFlowHedge:...'.
const M: Month = new Month({
date: new Date(),
comment: null,
expectedCashFlow: 0,
hedgeRatio: 0,
expectedCashFlowHedge: 0,
categories: []
})
export class Month {
date: Date;
expectedCashFlow: number;
hedgeRatio: number;
expectedCashFlowHedge: number;
categories: Category[] = [];
comment: string;
constructor(x: Month) {
if (x) {
this.date = x.date;
this.expectedCashFlow = x.expectedCashFlow;
this.hedgeRatio = x.hedgeRatio;
this.expectedCashFlowHedge = x.expectedCashFlowHedge;
x.categories.forEach(
res => this.categories.push(
new Category(res)
)
);
this.comment = x.comment;
}
}
get hasAdditionalCategories(): boolean {
return this.categories.length > 1;
}
get hasComment(): boolean {
if (!this.comment) {
return false;
} else {
return this.comment.length > 0;
}
}
reset(setting: IExposureSetting): void {
this.resetCashFlows();
this.resetHedgeRatio(setting);
this.resetComment();
this.resetCategories(setting);
}
private resetComment(): void {
this.comment = null;
}
private resetCategories(setting: IExposureSetting): void {
this.categories = [];
const cat: Category = new Category({
category: setting.assumedOriginOfExposure,
hedgeRatio: setting.defaultHedgeRatio,
expectedCashFlow: 0,
expectedCashFlowHedge: 0
});
this.categories.push(cat);
}
private resetCashFlows(): void {
this.expectedCashFlow = 0;
this.expectedCashFlowHedge = 0;
}
private resetHedgeRatio(setting: IExposureSetting): void {
this.hedgeRatio = setting.defaultHedgeRatio;
}
}
You're misunderstanding how types work.
A Month means something that looks exactly like a Month, which methods and everything else. As far as the type system is concerned, your declaration means that the ctor can call any Month method on its parameter.
Incidentally, that makes your class impossible to construct.
Instead, if you want to take this approach, you can move the fields to a base class or interface and make the constructor take that.
Since the constructor takes an instance of the class as a parameter it is imposible to construct (not without casting to any anyway).
There are several solutions depending on how safe you want to be and how muck you want to write:
The anonymous interface approach
You can define the argument's structure inline in the constructor declaration:
constructor(x: {date: Date;
expectedCashFlow: number;
hedgeRatio: number;
expectedCashFlowHedge: number;
categories: Category[];
comment: string;}) { ... }
Partial<T>
Partial<T> is a type with the same structure as T but with all properties marked as optional. This approach has the disadvantage that you can't control which arguments are required, and since it has the same structure as T you can even specify some functions. The advantage is you don't have to specify names and types again and if you add any more fields they will appear in the constructor argument automatically
constructor(x: Partial<Month>) { ... }
new Month({
comment: "", // we can specify public any field
reset() { } // But also methods which is not ideal
})
Pick<T, K extends keyof T>
Pick allows us to define a type with the property names defined by K (which can only contain strings that are keys for T) and the same types as those keys have in T
constructor(x: Pick<Month, "categories" | "date" | "hedgeRatio" | "expectedCashFlow" | "expectedCashFlowHedge" | "comment">) { ... }
The advantage is that you can control what properties are exposed in the constructor, all the properties are required, and that if a property gets removed or renamed, the compiler will throw an error that the property is no longer part of T
new Month({
// we can specify all fields in the Pick
comment: "",
date: new Date(),
categories: [],
expectedCashFlow: 0,
hedgeRatio: 0,
expectedCashFlowHedge: 0
})
The anonymous interface approach and use the arg as field
Finally the last solution would be to change the structure of the class and keep all properties in a separate state field that is the arg to the constructor and use that in all methods:
constructor(public state: {date: Date;
expectedCashFlow: number;
hedgeRatio: number;
expectedCashFlowHedge: number;
categories: Category[];
comment: string;}) { ... }
Is there a way of setting a null to calling instance as a result of some "macro-function-call"?
Like so:
class A {
// ...
macro function DestroyItself() {
// ...
}
}
var a:A = new A();
// ...
a.DestroyItself();
trace(a); // "null"
Yep:
macro public function destroy(self:Expr) {
return macro $self = null;
}
// ...
a.destroy();
In non-static macro functions first Expr argument is a ref to caller instance.
Once approach would be to create generic tools to null any instance.
package ;
class Tools
{
/**
* Simply assigns null to the instance
* See more at: http://code.haxe.org/category/macros/generating-code-in-a-macro.html
*
* #param instance - Any
* #return haxe.macro.Expr
*/
public static macro function nullMe(instance : haxe.macro.Expr.ExprOf<Dynamic>) : haxe.macro.Expr
{
return macro {
${instance} = null;
};
}
}
This use the using Tools; to generically null any instance, but I would not recommend this. I'd use the per-class approach.
Main.hx
package ;
class Main {
static function main() {
// Construct
var instance = new SomeClass();
// Destroy
instance.destroy();
// Trace null
trace(instance);
}
}
SomeClass.hx
package ;
class SomeClass
{
public function new()
{
trace("Hello from SomeClass!");
}
private function preDestroy()
{
trace("The end is nigh!");
}
public macro function destroy(self : haxe.macro.Expr) : haxe.macro.Expr
{
return macro {
#:privateAccess ${self}.preDestroy();
${self} = null;
};
}
}
Compiled JS
// Generated by Haxe 3.4.2
(function () { "use strict";
var Main = function() { };
Main.main = function() {
var instance = new SomeClass();
instance.preDestroy();
instance = null;
console.log(instance);
};
var SomeClass = function() {
console.log("Hello from SomeClass!");
};
SomeClass.prototype = {
preDestroy: function() {
console.log("The end is nigh!");
}
};
Main.main();
})();
I'm making a plugin for Aurelia and need a class decorator that
adds attributes to the new object instance, and
calls an external function with the new object as an argument.
I've looked through examples, and so far I've put together ("pseudo-ish" code)
return function addAndCall(target: any): any {
var original = target;
var newConstructor = function (...args) {
original.apply(this, args);
this.newAttribute = "object instance value";
ExternalModule.externalFunction(this);
};
newConstructor.prototype = Object.create(original.prototype);
newConstructor.prototype.constructor = original;
return <any>newConstructor;
}
but
I'm not entirely clear on the details here (or what is actually needed), and
it might not work properly since I'm getting Aurelia errors when using objects instantiated from classes with this decorator (and I suspect it's my decorator rather than the Aurelia framework that's buggy).
Any help and explanation would be greatly appreciated!
Why not just assign those properties to the prototype, and subsequently assign to the instance on first invocation
// decorator
function addAndCall(cb: Function, newField: string) {
// cb is now available in the decorator
return function(ctor: Function): void {
Object.defineProperty(ctor.prototype, newField, {
value: function(...args: any[]) {
return Object.defineProperty(this, newField, {
value: function(...args: any[]) {
console.log(newField, ...args);
}
})[newField](...args);
}
});
cb(ctor);
}
}
let callMe = (decoratedCtor) => console.log(decoratedCtor);
#addAndCall(callMe, 'propertyName')
class AddToMe {}
let addToMe = new AddToMe();
(<any>addToMe).propertyName(1, 2);
Here's a working version:
function addAndCall(target: any) {
var original = target;
function construct(constructor, args) {
var c: any = function () {
this.newAttribute = "object instance value";
ExternalModule.externalFunction(this);
return constructor.apply(this, args);;
}
c.prototype = constructor.prototype;
return new c();
}
var f: any = function (...args) {
return construct(original, args);
}
f.prototype = original.prototype;
return f;
}
(code in playground)
I have a fn that inherit an existing fn ( take Angular1 $q for example )
//$q original behavior
var defer = $q.defer();
defer.promise.then(function(result){})
//or
$q( (resolve, reject) => {
//promise execution here
}).then(function(result){});
If I want to decorate it, I would do :
var Qdecorator = function($delegate) {
var Q = function(resolver:any): any {
//do some extra stuff here
return $delegate.apply($delegate, arguments);
}
//Assign the static methods here:
Q.defer = function() {
//do some stuff
return $delegate.defer.apply($delegate, []);
}
//same goes for race, when, resole reject and so on
return Q;
}
Problem is that typescript complains about
Property defer, race, when, resolve, etc... does not exist on type '(resolver: any) => any'
I tried to use the IQService, and IPromise with no luck, btu I'd like to raise a more global question :
How do I define late static methods on function() that return an object without using new
I am copying pasting the answer to my question from this link:
https://www.typescriptlang.org/docs/handbook/interfaces.html
interface Counter {
(start: number): string;
interval: number;
reset(): void;
}
function getCounter(): Counter {
let counter = <Counter>function (start: number) { };
counter.interval = 123;
counter.reset = function () { };
return counter;
}
let c = getCounter();
c(10);
c.reset();
c.interval = 5.0;
I have this interface :
interface IPoint {
getDist(): string;
getDist(x: number): any;
}
and I need a class to implement it but I can't get the right syntax to implement the getDist() method
in the class..
class Point implements IPoint {
// Constructor
constructor (public x: number, public y: number) { }
pointMethod() { }
getDist() {
Math.sqrt(this.x * this.x + this.y * this.y);
}
// Static member
static origin = new Point(0, 0);
}
it says:
Class 'Point' declares interface 'IPoint' but does not implement it:
Types of property 'getDist' of types 'Point' and 'IPoint' are
incompatible:Call signatures of types '() => void' and '{ (): string;
(x: number): any; }' are incompatible
What's the proper way to do this?
Thanks
When you declare the function in the class you need to decorate it with the overloads:
getDist(): string;
getDist(x: number): any;
getDist(x?: number): any {
// your code
}
This answer describes how to implement method overloading in TypeScript, and it's not pretty:
interface IPoint {
getDist(): string;
getDist(x: number): any;
}
class Point implements IPoint {
// Constructor
constructor (public x: number, public y: number) { }
pointMethod() { }
getDist(x?: number) {
if (x && typeof x == "number") {
return 'foo';
} else {
return 'bar';
}
}
}
N.B. with the particular combination of declared return types in the interface, you are limited to returning strings from getDist.
also you can use the default value
interface Foo{
next()
next(steps: number)
prev()
prev(steps: number)
}
next(steps: number = 1) {
// ...
}
prev(steps: number = 1) {
// ...
}
The following is a variation on some of the above
class Position {
x: number;
y: number;
constructor(x : number = 0, y : number = 0) {
this.x = x;
this.y = y;
}
}
class Pen {
colour: string;
constructor(colour: string) {
this.colour = colour;
}
}
class PlottingHead {
isPenUp: boolean;
pen: Pen;
position: Position;
constructor() {
this.penUp();
}
move(p: Position): void;
move(x: number, y: number): void;
move(x: number | Position, y?: number): void {
if (typeof x === "number")
{
x = new Position(x, y);
}
this.penUp();
this.position = x;
}
draw(x: number | Position, y?: number): void {
if (typeof x === "number")
{
x = new Position(x, y);
}
this.penDown();
this.position = x;
}
penDown(): void {
this.isPenUp = false;
}
penUp(): void {
this.isPenUp = true;
}
onChangePen(newPen: Pen) {
this.penUp();
this.pen = newPen;
}
}
The move function takes either a single position object or a pair of numeric values. This can obviously be extended as required.