Typescript pass property by reference

3.9k views Asked by At

I have a Typescript class with 4 different properties, like this:

class MyClass {
     private x: number;
     private y: number;
     private z: number;
     private w: number;
}

I want to create four functions that increment these properties:

   incrementX() { this.x++; }
   incrementY() { this.y++; )
   ...

However, I don't want the increment logic (++) to be duplicated, I want to place it in one function. If Typescript had ref parameters like C#, I would do something like:

   incrementX() { this.increment(ref this.x); }
   increment(p: ref number) { p++; }

Typescript does not support passing by reference. The non type-safe way of implementing this is:

   incrementX() { this.increment("x"); }
   increment(p: string) {
       const self = this as any;
       self[p]++;
   }

It's not type-safe. I can easily call increment('not-a-property') without getting an error from the compiler. I've added a runtime check to make sure self[p] is indeed a number, but I still want something the compiler can catch.

Is there a type-safe way of implementing this?

Note: obviously my actual code doesn't increment numbers, but rather does something quite elaborate - not on numbers but on another class type.

2

There are 2 answers

3
ed' On BEST ANSWER

You could use keyof and number extends maybe? To allow passing only keys of the class which are numbers.

Playground here

class MyClass {
  public a: number = 0;
  public b: number = 0;
  public c: string = "";

  public increment(
     key: {
      [K in keyof MyClass]-?: number extends MyClass[K] ? K : never
    }[keyof MyClass]
  ) {
    this[key]++;
  }
}

const test = new MyClass();

test.increment("a");
test.increment("b");
test.increment("c"); // fail
test.increment("d"); // fail
2
Richard Haddad On

A solution would be to type p with keyof MyClass.

     increment(p: keyof MyClass): void {
       this[p]++;
     }

But it wont work. Because your number fields are private, and because in keys of MyClass you have the function increment itself.

A solution would be to extract only fields that are numbers:

type OnlyNumberKeys<O> = {
  [K in keyof O]: O[K] extends number ? K : never;
}[keyof O];

Then use this type in increment function:

class MyClass {
     x: number;
     y: number;
     z: number;
     w: number;

     increment(p: OnlyNumberKeys<MyClass>): void {
       this[p]++;
     }
}

Now p accepts only 'x' | 'y' | 'z' | 'x'.

Note that I had to remove all private keywords. I don't know if there is a solution for that.