ChatGPT解决这个技术问题 Extra ChatGPT

How to implement class constants?

In TypeScript, the const keyword cannot be used to declare class properties. Doing so causes the compiler to an error with "A class member cannot have the 'const' keyword."

I find myself in need to clearly indicate in code that a property should not be changed. I want the IDE or compiler to error if I attempt to assign a new value to the property once it has been declared. How do you guys achieve this?

I'm currently using a read-only property, but I'm new to Typescript (and JavaScript) and wonder whether there is a better way:

get MY_CONSTANT():number {return 10};

I'm using typescript 1.8. Suggestions?

PS: I'm now using typescript 2.0.3, so I've accepted David's answer


D
David Sherret

TypeScript 2.0 has the readonly modifier:

class MyClass {
    readonly myReadOnlyProperty = 1;

    myMethod() {
        console.log(this.myReadOnlyProperty);
        this.myReadOnlyProperty = 5; // error, readonly
    }
}

new MyClass().myReadOnlyProperty = 5; // error, readonly

It's not exactly a constant because it allows assignment in the constructor, but that's most likely not a big deal.

Alternative Solution

An alternative is to use the static keyword with readonly:

class MyClass {
    static readonly myReadOnlyProperty = 1;

    constructor() {
        MyClass.myReadOnlyProperty = 5; // error, readonly
    }

    myMethod() {
        console.log(MyClass.myReadOnlyProperty);
        MyClass.myReadOnlyProperty = 5; // error, readonly
    }
}

MyClass.myReadOnlyProperty = 5; // error, readonly

This has the benefit of not being assignable in the constructor and only existing in one place.


To access the properties from outside the class, you'll need to add the export keyword before class as well as public static before the readonly keyword. See here: stackoverflow.com/a/22993349
Question. Was clueless why you need the class name to use that readOnly property inside the class itself? 'MyClass.myReadonlyProperty'
@SaiyaffFarouk If I understand your question, the answer is that static properties exist as part of the class, not on an instance of the class. So, you access them using the class name not a variable which contains a class instance.
The export (external modules) and public keyword are unrelated to this question/answer, but on the topic of explicitness, I personally find it extremely easy to tell that a member is public when the keyword doesn't exist. I don't bother with it for that reason and because it adds more noise & is needless typing. It also makes the public members more distinct from ones marked as private or protected. Anyway, just my opinion :)
What about anonymous classes ? Any ideas on how to access static readonly myReadOnlyProperty when class is declared with export default class { ... } ? Tried this.myVar, self.myVar, static, default... not working... (EDIT: default.myVar seems to be the solution, but I'm getting a type error)
j
j3ff

Constants can be declare outside of classes and use within your class. Otherwise the get property is a nice workaround

const MY_CONSTANT: string = "wazzup";

export class MyClass {

    public myFunction() {

        alert(MY_CONSTANT);
    }
}

Thanks; I'm worried about this implementation because it's not portable (in the model, the constant isn't actually part of the class) and it leaks info into the greater scope, but it has the advantage of being a real constant so I won't be able to change it without raising alarm bells.
I understand the concern and I find the use of get property very appropriate in your case
Per angular.io/docs/ts/latest/guide/style-guide.html please use camel caase instead of upper case. Upper case for constants is not recommended.
Angular styleguide, not TypeScript styleguide.. Question was regarding TypeScript specifically
@Esko I believe that in typescript the const is limited to the file because each file is a module. To make it accessible outside, you would need to declare it with export const and then import it from another file. Would be pretty easy to test though. Just declare a const in one file, and try to use it in another without export/import, or to use it from the browser console.
a
am0wa

You can mark properties with readonly modifier in your declaration:

export class MyClass {
  public static readonly MY_PUBLIC_CONSTANT = 10;
  private static readonly myPrivateConstant = 5;
}

@see TypeScript Deep Dive book - Readonly


P
Parth Ghiya

Angular 2 Provides a very nice feature called as Opaque Constants. Create a class & Define all the constants there using opaque constants.

import { OpaqueToken } from "@angular/core";

export let APP_CONFIG = new OpaqueToken("my.config");

export interface MyAppConfig {
    apiEndpoint: string;
}

export const AppConfig: MyAppConfig = {    
    apiEndpoint: "http://localhost:8080/api/"    
};

Inject it in providers in app.module.ts

You will be able to use it across every components.

EDIT for Angular 4 :

For Angular 4 the new concept is Injection Token & Opaque token is Deprecated in Angular 4.

Injection Token Adds functionalities on top of Opaque Tokens, it allows to attach type info on the token via TypeScript generics, plus Injection tokens, removes the need of adding @Inject

Example Code

Angular 2 Using Opaque Tokens

const API_URL = new OpaqueToken('apiUrl'); //no Type Check


providers: [
  {
    provide: DataService,
    useFactory: (http, apiUrl) => {
      // create data service
    },
    deps: [
      Http,
      new Inject(API_URL) //notice the new Inject
    ]
  }
]

Angular 4 Using Injection Tokens

const API_URL = new InjectionToken<string>('apiUrl'); // generic defines return value of injector


providers: [
  {
    provide: DataService,
    useFactory: (http, apiUrl) => {
      // create data service
    },
    deps: [
      Http,
      API_URL // no `new Inject()` needed!
    ]
  }
]

Injection tokens are designed logically on top of Opaque tokens & Opaque tokens are deprecated in Angular 4.


minus one. This question has nothing to do with Angular. It is requesting a TypeScript solution.
R
Rycochet

All of the replies with readonly are only suitable when this is a pure TS environment - if it's ever being made into a library then this doesn't actually prevent anything, it just provides warnings for the TS compiler itself.

Static is also not correct - that's adding a method to the Class, not to an instance of the class - so you need to address it directly.

There are several ways to manage this, but the pure TS way is to use a getter - exactly as you have done already.

The alternative way is to put it in as readonly, but then use Object.defineProperty to lock it - this is almost the same thing that is being done via the getter, but you can lock it to have a value, rather than a method to use to get it -

class MyClass {
    MY_CONSTANT = 10;

    constructor() {
        Object.defineProperty(this, "MY_CONSTANT", {value: this.MY_CONSTANT});
    }
}

The defaults make it read-only, but check out the docs for more details.


W
Willem van der Veen

For this you can use the readonly modifier. Object properties which are readonly can only be assigned during initialization of the object.

Example in classes:

class Circle {
  readonly radius: number;

  constructor(radius: number) {
    this.radius = radius;
  }

  get area() {
    return Math.PI * this.radius * 2;
  }
}

const circle = new Circle(12);
circle.radius = 12; // Cannot assign to 'radius' because it is a read-only property.

Example in Object literals:

type Rectangle = {
  readonly height: number;
  readonly width: number;
};

const square: Rectangle = { height: 1, width: 2 };
square.height = 5 // Cannot assign to 'height' because it is a read-only property

It's also worth knowing that the readonly modifier is purely a typescript construct and when the TS is compiled to JS the construct will not be present in the compiled JS. When we are modifying properties which are readonly the TS compiler will warn us about it (it is valid JS).


K
Krishna Ganeriwal

Either use readOnly modifier with the constant one needs to declare or one might declare a constant outside the class and use it specifically only in the required class using get operator.


d
dgzornoza

If what you want is to encapsulate constants, it doesn't have to be a class, it can be a namespace, here are the different options:

const MY_CONST_1 = "MyText"

This is the most optimal option and generates the following js:

const MY_CONST_1 = "MyText"

other opcion is encapsulate in namespace:

namespace Constants {
  export const MY_CONST_1: string = 'MyText';  
}

this generate the following js:

var Constants;
(function (Constants) {
    Constants.MY_CONST_1 = 'MyText';
})(Constants || (Constants = {}));

and other options with class:

abstract class ConstantsClass {
    static readonly MY_CONST_1 = "MyText";
}

this generate the following js:

class ConstantsClass {
}
ConstantsClass.MY_CONST_1 = "MyText";

you can choose the best for you.


m
matreurai

You could also simply declare in lets say: src/classes/Car/consts/Honda.ts

export const CIVIC: string = "Civic";

export default {
    CIVIC: CIVIC
};

Then in your class file do something like:

import Honda from "./consts/Honda";

export class Car {

    protected model: string = Honda.Civic;

    [...]

}

export default Car;

This way, you are making sure it's a constant. This method also works like a charm if you are building a dataset.


J
Janne Harju

For me none of earlier answer works. I did need to convert my static class to enum. Like this:

export enum MyConstants {
  MyFirstConstant = 'MyFirstConstant',
  MySecondConstant = 'MySecondConstant'
}

Then in my component I add new property as suggested in other answers

export class MyComponent {
public MY_CONTANTS = MyConstans;
constructor() { }
}

Then in my component's template I use it this way

<div [myDirective]="MY_CONTANTS.MyFirstConstant"> </div>

EDIT: Sorry. My problem was different than OP's. I still leave this here if someelse have same problem than I.


Using an enum to save constants isn't a good practice in any language.
It is best solution for solutions available currently. I know it is how enum should not be used but with Angular it is cleanest way to have bindable constants.