0

First and foremost: It could very well be that the answer is out there but I don't know how it's called so I sadly couldn't find it.

That out of the way; My problem is that I want to edit a function's properties in typescript (declare them, because it says they're undeclared) but don't know how to do that.

In just JavaScript you can do:

console.log('logError00 ==============================');
logError00('Foo');
logError00('Bar');
logError00('hey');

function logError00(error) {
    const _this = logError00;
    if(_this.counter === undefined) _this.counter = 1;
    else _this.counter++;
    console.log(`${_this.counter} | ${error}`);
}

or (this is maybe not sensible as it needs to be declared before the function call):

logError01.counter = 0;
function logError01(error) {
    const _this = logError01;
    _this.counter++;
    console.log(`${_this.counter} | ${error}`);
}

console.log('logError01 ==============================');
logError01('Foo');
logError01('Bar');
logError01('hey');

And what I'm trying to do in the end is creating a Class that keeps track of how meany times a method was called without declaring variables like:

class MyClass {
    private func1Count = 0;
    private func2Count = 0;
    [...]
}

So this is what I got working in JS:

class MyError {
	constructor(message) {
		this.log.counter = 0;
		this.message = message;
	}

	log() {
		this.log.counter++;
		console.log(`${this.log.counter} | ${this.message}`);
	}
}



console.log('MyError (class) ==============================');
const myError = new MyError('Some Message');
myError.log();
myError.log();
myError.log();

And this is what is I'm trying to prevent but compiles fine:

wouldBeMeh('Foo');
wouldBeMeh('Bar');
wouldBeMeh('Hey!');

function wouldBeMeh(error: string) {
    const _this = (wouldBeMeh as any);

    if(_this.counter === undefined) _this.counter = 1;
    else _this.counter++;

    console.log(`${_this.counter} | ${error}`);
}

Thanks in advance.

2
  • "So this is what I got working in JS"... try creating two of them: const myError = new MyError('Some Message'); myError.log(); const myError2 = new MyError('Other Message'); myError.log(); myError2.log(); myError.log(); You might be surprised. What are you trying to count? Commented Aug 29, 2019 at 14:29
  • Are you trying to keep a single count for all instances of MyError? Or are you trying to keep a separate count for each instance of MyError? Either way the code you have doesn't quite do it. Commented Aug 29, 2019 at 14:42

2 Answers 2

1

You say you don't want to declare member variables like func1Count, etc, but that really is the most straightforward way someone would do something like this, especially because it uses properties and methods the way the TypeScript compiler understands. If you can explain why you don't want to use member variables, it's possible someone could come up with an implementation which meets your needs and doesn't have to jump through too many hoops.

For now I'll assume you really need to add a property to a method (and not a standalone function, support for which was added in TS3.1). TypeScript doesn't generally allow expando properties; if you want to add a property to something, its type needs to be known to have that property.

The easiest way to do this for something like a function which can't be created via an object literal is with Object.assign(). Something of the form

const funcWithProp = Object.assign(function(){}, {prop: ""});

will be inferred to have the type

// const funcWithProp: (() => void) & { prop: string; }

and can be used both as a function and as a prop-keyed object:

funcWithProp(); // okay
funcWithProp.prop = "hey" // okay

So, let's look at your class and see what to do:


I'm not sure whether you want to have a single counter that increments whenever any instance of MyError has its log() method called, or if you want a separate counter for each instance of MyError. So I'll implement both.

If you want a single counter, you should do it like this:

class MyError {
  message: string;
  constructor(message: string) {
    this.message = message;
  }
}
interface MyError {
  log: ((this: MyError) => void) & { counter: number };
}
MyError.prototype.log = Object.assign(
  function(this: MyError) {
    this.log.counter++;
    console.log(`${this.log.counter} | ${this.message}`);
  },
  { counter: 0 }
);

Note that I had to use declaration merging and set log directly on MyError.prototype. Methods usually end up on the prototype (there's only one of them per class), and when you declare a method inside a class, there's no way to annotate that you'd like it to have more properties. So the proper annotation needs to go inside interface MyError, which is merged into the class, and the actual implementation is assigned to MyError.prototype.log and uses Object.assign(). Also notice that the log function signature features a this parameter. That is just part of the type system, and it lets the compiler know that you can only call the function as a method on an object of the MyError type, and that the implementation of the function can access this as an instance of MyError.

Let's see if it works:

const myError = new MyError("Some Message");
myError.log(); // 1 | Some Message
const myError2 = new MyError("Other Message");
myError.log(); // 2 | Some Message
myError2.log(); // 3 | Other Message
myError.log(); // 4 | Some Message

Looks good.


What if you want each MyError instance to have its own log counter? We'd do it this way:

class MyError {
  message: string;
  constructor(message: string) {
    this.message = message;
  }

  log = Object.assign(
    function(this: MyError) {
      this.log.counter++;
      console.log(`${this.log.counter} | ${this.message}`);
    },
    { counter: 0 }
  );
}

Now we can't use a normal method that goes on the prototype, or there'd be only one of them for the whole MyError class. You want to keep track of each instance's call to log(), meaning you need separate counter properties for each instance. But you can only do that if you have separate log() implementations for each instance... (well, unless you use MyError member variables, but you don't want to do that). So instead of making log a method, we make it a function-valued instance property, initialized with =. We use the same Object.assign(...) code from before, and it just works.

Well, let's see if it works:

const myError = new MyError("Some Message");
myError.log(); // 1 | Some Message
const myError2 = new MyError("Other Message");
myError.log(); // 2 | Some Message
myError2.log(); // 1 | Other Message
myError.log(); // 3 | Some Message 

Looks good.


All right, hope that helps; good luck!

Link to code

Sign up to request clarification or add additional context in comments.

4 Comments

This is a beautiful answer! It is full of new knowledge and ideas, very well done. I was asking to have such a counter as I have an interface to some sort of data server and there are different fetch method. Now I wanted to count how many times each function was called with the same arguments and throw an error after reaching the limit. My thought was that such a variable is best hidden away and so one. If you say it is best practice to just implement it like: private _counterForSomething: number I'll do that :) I'm sadly not yet that good at typescript or programming in general. Ty again.
And sorry that I don't respond to the comments I was traveling at that point in time.
Why exactly is a variable saved in a property as I did in JavaScript not unique to the object? Maybe you have some reference I could read?
When log is a method defined like class Foo{log(){}}, it ends up as a property of the class prototype, and each instance inherits the method from the prototype. Even if a and b are different Foo instances, a.log and b.log are the same object. And thus this.log.counter refers to the same value regardless of whether this is a or b or something else. Maybe as a reference you should read about prototypical inheritance in JavaScript both with and without class syntax.
1

For a function this just works. Typescript lets you add members to a function in the current scope:



wouldBeMeh.counter = 0;
function wouldBeMeh(error: string) {


    if(wouldBeMeh.counter === undefined) wouldBeMeh.counter = 1;
    else wouldBeMeh.counter++;

    console.log(`${wouldBeMeh.counter} | ${error}`);
}


wouldBeMeh('Foo');
wouldBeMeh('Bar');
wouldBeMeh('Hey!');

Play

1 Comment

And I just checked, and trying to do this to a method (I tried in the constructor) (which is my actual goal as stated), does not work.

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.