When a property is designated as readonly in a TypeScript class, it can still be modified despite its intended

I'm currently grappling with the concept of the readonly keyword in TypeScript. As far as I understand, a readonly property should not be able to be written to after the constructor is called. However, in my testing, I've found that I can actually overwrite it. The compiler does throw errors like "Cannot assign to 'doNoTChange' because it is a read-only property" and "Property 'doNoTChange' is private and only accessible within class 'readOnlyClass'," but despite these warnings, the code compiles and runs. So, my question is: is this behavior normal, and is there a way to prevent overwriting?

Just to clarify, I'm using TypeScript in a Vue environment.

The following is the exact code I've been testing. I've been conducting these tests within the mounted hook of the App.vue file (meaning I haven't altered the default file structure provided by the Vue CLI).

My setup involves Vue 2.6.11 and TypeScript version 3.5.3.

mounted() {
   /** Class definition */
   class readOnlyClass {
       readonly doNoTChange: string;
       constructor(ss: string) {
        this.doNoTChange = ss;
       }
   }

   /** Creating and logging object */
   let test = new readOnlyClass('aaa'); 
   console.log(test.doNoTChange); // outputs 'aaa'

   /** Overwriting and logging the changed property */
   test.doNoTChange = 'after change';
   console.log(test.doNoTChange); // outputs 'after change'
}

Answer №1

When TypeScript code is compiled using tsc, it undergoes both type checking and transpilation to JavaScript, with these processes being mostly independent of each other.

The main purpose of type checking is to alert the developer to potential issues that could cause problems at runtime. It's important to note that these alerts are just warnings; they don't actually prevent runtime problems. For example, when attempting to assign to a readonly property, a warning is generated:

Cannot assign to 'doNoTChange' because it is a read-only property.(2540)

This warning signals that there might be an issue that needs to be addressed, but the compiler itself does not fix the problem (which might require more advanced logic that a compiler cannot provide) by generating code without the problem.

The transpilation process involves converting TypeScript code to JavaScript by removing static type system features and generating runtime code based on the target version of JavaScript specified in the compiler options. This means that features like readonly, which exist purely in the type system, do not appear in the resulting JavaScript code. The compiler can still produce JavaScript even if the type checker identifies errors, as this is an intentional design choice.

If you wish for the compiler to not output JavaScript in case of errors, you can utilize the --noEmitOnError compiler option. If you encounter issues with this option, it's advisable to carefully review your compiler configuration and consider creating a reproducible example to help others identify the problem.


Delving deeper, the challenges you're facing may relate to type erasure in general. In TypeScript, it's beneficial to think about the desired runtime behavior first and then use TypeScript to provide stronger types for guidance during development. JavaScript, at runtime, will attempt to execute x.toUpperCase() regardless of the type of x. By defining let x: string; and later assigning x = 15; x.toUpperCase(), the compiler will warn you when you assign

x = 15</code, indicating a potential issue.</p>

<p>To achieve the desired runtime behavior, consider using JavaScript <a href="https://developer.mozilla.org/en-US/docs/Web/JavaScript/Guide/Working_with_Objects#Defining_getters_and_setters" rel="nofollow noreferrer">getters</a> for properties like <code>doNoTChange
. By creating a getter without a setter, attempting to set the property will result in a runtime error. A sample implementation could be:

class readOnlyClass {
  private _val: string;
  get doNoTChange(): string {
    return this._val;
  }
  constructor(ss: string) {
    this._val = ss;
  }
}

When targeting ES2017, the generated code would be:

class readOnlyClass {
    constructor(ss) {
        this._val = ss;
    }
    get doNoTChange() {
        return this._val;
    }
}

Resulting in the following behavior:

let test = new readOnlyClass('aaa');
console.log(test.doNoTChange); // outputs 'aaa'

test.doNoTChange = 'after change'; // compilation error, and at runtime:
// TypeError: setting getter-only property "doNoTChange"
console.log(test.doNoTChange); 

This setup leads to a compiler error occurring alongside a runtime error.


Hopefully, this guidance sheds light on your situation. Best of luck!

Link to code

Answer №2

Big shoutout to @jcalz for the helpful answer and @marty for the insightful comment. They both stressed the importance of reviewing my compiler options, which turned out to be the key to solving my issue. However, navigating webpack and TypeScript isn't my forte, so the solution wasn't immediately clear to me.




Step 1:

Following the advice of @jcalz and @marty, I added the --noEmitOnError compiler option to my tsconfig.json file. Although this change didn't resolve the problem on its own, it paved the way for step 2.



Step 2:

Upon stumbling upon the same issue on GitHub at this link https://github.com/vuejs/vue-cli/issues/5014, I discovered the solution lay in disabling the async option in https://github.com/TypeStrong/fork-ts-checker-webpack-plugin#options. This led me to delve into tweaking my webpack build, and after some exploration, I uncovered the missing piece of the puzzle: adding specific code to my vue.config.js file.

My vue.config.js file now includes:

module.exports = {
    lintOnSave: 'error',
    chainWebpack: config => {
        config.module
            .rule('ts')
            .test(/\.tsx?$/)
            .use('ts-loader')
            // .loader('ts-loader')
            .tap( _ => {
                return {
                    appendTsSuffixTo: [/\.vue$/],
                    // transpileOnly: true
                };
            })
            .end();
    },
}

As you can see, I opted to comment out the line transpileOnly: true. This move was crucial as it prevented errors I initially overlooked. Setting it to true would have resulted in compilation despite errors, as highlighted in this resource https://github.com/TypeStrong/ts-loader#transpileonly, cautioning that "many benefits of static type checking across dependencies could be lost."

Now, my code fails as expected, signaling progress in resolving the issue.

Similar questions

If you have not found the answer to your question or you are interested in this topic, then look at other similar questions below or use the search

How can I move the cursor to the beginning of a long string in Mat-autocomplete when it appears at the end?

I'm struggling to figure out how to execute a code snippet for my Angular app on Stack Overflow. Specifically, I am using mat-autocomplete. When I select a name that is 128 characters long, the cursor appears at the end of the selected string instead ...

When configuring Gatsby with Typescript, you may encounter the error message: "You cannot utilize JSX unless the '--jsx' flag is provided."

I am currently working on a Gatsby project and decided to implement Typescript into it. However, I encountered an error in my TSX files which reads: Cannot use JSX unless the '--jsx' flag is provided. What have I tried? I consulted the docume ...

Error: Idle provider not found in the promise

Currently, I am integrating ng2-idle into an AngularJS 2 application. After successfully including the ng2-idle package in the node_modules directory of my project, I attempted to import it into one of my components as shown below: Dashboard.component.ts: ...

How to have Angular open a PDF file in a new tab

Currently, I am working on implementing the functionality to open a PDF file in a new tab using Angular 9. The PDF file is received from an API as a blob. However, I have encountered an issue due to the deprecation of window.URL.createObjectURL(blob);. Thi ...

Errors during TypeScript compilation in Twilio Functions

When I run npx tsc, I encounter the following errors: node_modules/@twilio-labs/serverless-runtime-types/types.d.ts:5:10 - error TS2305: Module '"twilio/lib/rest/Twilio"' does not export 'TwilioClientOptions'. 5 import { Twil ...

The error message "Type 'string' cannot be assigned to type 'Condition<UserObj>' while attempting to create a mongoose query by ID" is indicating a type mismatch issue

One of the API routes in Next has been causing some issues. Here is the code: import {NextApiRequest, NextApiResponse} from "next"; import dbConnect from "../../utils/dbConnect"; import {UserModel} from "../../models/user"; e ...

Is it possible to minimize the number of accessors needed for reactive forms?

Currently, I am dealing with a reactive form that consists of 20 different inputs. An example of one input is shown below: <input formControlName="name" matInput> For each input, I find myself needing to write an accessor function like the ...

Vue's watch function failing to trigger

Experiencing issues with Vue watch methods not triggering for certain objects even when using deep:true. Within my component, I am passed an array as a prop containing fields used to generate forms. These forms are dynamically bound to an object named cru ...

Issues with Webpack and TypeScript CommonsChunkPlugin functionality not functioning as expected

Despite following various tutorials on setting up CommonsChunkPlugin, I am unable to get it to work. I have also gone through the existing posts related to this issue without any success. In my project, I have three TypeScript files that require the same ...

Trying to access a private or protected member 'something' on a type parameter in Typescript is not permitted

class AnotherClass<U extends number> { protected anotherMethod(): void { } protected anotherOtherMethod(): ReturnType<this["anotherMethod"]> { // Private or protected member 'anotherMethod' cannot be accessed on a type para ...

Tips for extracting year, month, and day from a date type in Typescript

I'm currently working with Angular 7 and I'm facing some challenges when it comes to extracting the year, month, and day from a Date type variable. Additionally, I am utilizing Bootstrap 4 in my project. Can anyone assist me with this? Below is ...

Implementing a feature in Typescript/React component that provides autocomplete functionality

Currently, I have developed a TypeScript and React component that has been published on NPM. My goal is to enable IntelliSense to autocomplete React props for this component. While I typically use JSDoc for plain React components, it does not seem to work ...

Properties of untyped objects in TypeScript are not defined

Here is the code snippet I've been working on: file.js const channel = {}, arr = [string,string,string]; for(let i = 0;i < arr.length;i++ ){ channel[arr[i]] = "Amo" //equal string value } I have an array that contains only string values, for ...

Understanding the Access-Control-Allow-Headers in preflight response for Wordpress and VueJS

Attempting to retrieve a route from candriam-app.nanosite.tech to candriam.nanosite.tech has proven challenging. Despite various attempts to allow headers, I continue to encounter this CORS error: Access to fetch at 'https://xxxA/wp-json/nf-submission ...

What could be causing the elements in my array to appear as undefined?

https://i.stack.imgur.com/ze1tx.png I'm stuck trying to understand why I can't extract data from the array. const usedPlatformLog: Date[] = [] users.forEach(el => { usedPlatformLog.push(el.lastUsed) }) console.log(usedPlatformLog) // disp ...

Filtering an array using criteria: A step-by-step guide

Currently, I am developing a system for Role Based permissions that involves working with arrays. Here is an example of the array structure I have: let Roles = { [ { model: 'user', property: 'find', permission: 'allow' ...

Using TypeScript to call Node.js functions instead of the standard way

Can someone assist me with the issue I'm facing? I have developed a default node.js app with express using Visual Studio nodejs tools, and now I am attempting to call the setTimeout function that is declared in node.d.ts. The code snippet in question ...

The assignment of Type Observable<Observable<any[]>> to Observable<any[]> is not valid

Working on implementing autocomplete using data from a database service: @Injectable() export class SchoolService { constructor(private db: AngularFirestore) { } getSchools(): Observable<School[]> { return this.db.collection<School> ...

I'm having trouble with the calculator, unable to identify the issue (Typescript)

I'm struggling with programming a calculator for my class. I followed the instructions from our lesson, but it's not functioning properly and I can't pinpoint the issue. I just need a hint on where the problem might be located. The calculat ...

To manipulate the array in Vue by adding and removing selected HTML elements

Sharing my code snippet: <form> <div v-for="(inputPlane, index) in inputsPlanes" :key="inputPlane.id" :id="`plane-${index}`"> <input placeholder="origin" name="data" /> < ...