Search code examples
javascripttypescript

Cannot assign to '' because it is a read-only property for static readonly property in typescript


Consider the following class:

export class Foo {
  id: string;

  static readonly questionKeyPrefix: string;

  constructor(id: string, _key: string) {
    this.id = id;

    if (!Foo.questionKeyPrefix) {
      // Error: Cannot assign to 'questionKeyPrefix' because it is a read-only property
      Foo.questionKeyPrefix = _key;
    }
  }
}

When I try to set questionKeyPrefix which is a static readonly property, it complians. I also add the considition to make sure it is set only once!

If I remove the static everything will be fine!

Any idea how to fix this issue?

Playground


Solution

  • The way your code is designed at the moment, it can't work, because you have a static property whose value can change every time the constructor is invoked. Such a property cannot be readonly because its value can be observed to change after initialisation.

    However, from the comments it seems this doesn't really model what you're trying to achieve, because you want this property to have a different value per subclass, just not a different value per instance.


    The simplest solution is to make it an instance property regardless, and just give that instance property the appropriate value based on its subclass. An instance property doesn't need to be a constructor parameter for every subclass; you can pass it from the subclass's constructor:

    class Base {
        // instance field
        readonly key: string;
        constructor(_key: string) {
            this.key = _key;
        }
    }
    
    class Foo extends Base {
        constructor() {
            super('Foo');
        }
    }
    
    class Bar extends Base {
        constructor() {
            super('Bar');
        }
    }
    

    Playground Link

    An alternative, if you are concerned about this being a property (e.g. because it shows up when you serialize the object), is to make this a "virtual" property with get:

    abstract class Base {
        abstract readonly key: string;
    }
    
    class Foo extends Base {
        get key() {
            return 'Foo';
        }
    }
    
    class Bar extends Base {
        get key() {
            return 'Bar';
        }
    }
    

    Playground Link

    Since the getter is declared per subclass, the value is per subclass rather than per instance.


    Generally speaking, "static per subclass" is not a very useful thing to do, because static properties are not normally accessed polymorphically. This would only make sense if the classes themselves were to be passed around as values, e.g.

    interface KeyedClass {
        readonly key: string;
    }
    
    class Foo {
        static readonly key = 'Foo';
    }
    
    class Bar {
        static readonly key = 'Bar';
    }
    
    function logKey(cls: KeyedClass) {
        console.log(cls.key);
    }
    
    logKey(Foo);
    logKey(Bar);
    

    Playground Link

    Assuming you aren't doing this, then there is no value in making something "static per class", because you can't access it polymorphically from an instance context. this.key won't work, and Base.key isn't polymorphic; you would just get the value of the base class's static property, regardless of what subclass this belongs to.