In TypeScript, the choice between using `private readonly` within a class and

I have been contemplating the best method and potential impacts of referencing constants from outside a class within the same file.

The issue arose when I was creating a basic class that would throw an error if an invalid parameter was passed:

export class Digit {
  private _value: number = null;

  constructor(value: number) {
    this._value = value;
  }

  set value(value: number) {
    if (isNaN(value)) { throw new Error('Received NaN as digit.'); }

    this._value = value;
  }

  get value() {
    return this._value;
  }

}

The setter for value wouldn't trigger in the constructor since the class must be instantiated first.

To keep things straightforward, I wanted this example class to only hold a valid digit or nothing at all, so initializing it with null on its value wasn't ideal. This led me to modify the code as follows:

export class Digit {
  private _value: number = null;

  constructor(value: number) {
    if (isNaN(value)) { throw new Error('Received NaN as digit.'); }

    this._value = value;
  }

  set value(value: number) {
    if (isNaN(value)) { throw new Error('Received NaN as digit.'); }

    this._value = value;
  }

  get value() {
    return this._value;
  }

}

This solution worked, but it involved repeating the validation for each field. Imagine having to validate 10 fields or more!

So I considered two alternatives:

1- Restructure the code to include a validation function within the class

export class Digit {
  private _value: number = null;

  private readonly validateValue = function (value: number): number {
    if (isNaN(value)) { throw new Error('Received NaN as digit.'); }

    return value;
  };

  constructor(value: number) {
    this._value = this.validateValue(value);
  }

  set value(value: number) {
    this._value = this.validateValue(value);
  }

  get value() {
    return this._value;
  }

}

I like this approach because all the logic is encapsulated within the class. However, the downside is that external entities can still access this logic if they disregard the private scope. Additionally, adding more fields and validations can clutter the class quickly, making it harder to focus on fixing specific behaviors while ensuring correct instance values.

2- Move the validation function outside of the class, but within the same file

const validateValue = function (value: number): number {
  if (isNaN(value)) { throw new Error('Received NaN as digit.'); }

  return value;
};

export class Digit {
  private _value: number = null;

  constructor(value: number) {
    this._value = validateValue(value);
  }

  set value(value: number) {
    this._value = validateValue(value);
  }

  get value() {
    return this._value;
  }

}

The benefit of this approach is being able to focus directly on the class code, knowing that the values are already validated without them cluttering the class. It also prevents accidental omission of a private scope to access validators.

If the validations become extensive, a helper function could be created for the class, and the validations could be moved out of the file (though I'm unsure if this is considered good practice).

However, I'm curious about the implications of using such external declarations outside of the class.

What would be the most suitable approach for this issue? Are both solutions viable, or is there a better way?

Answer №1

I would propose introducing a new type called NotNaN:

type NotNaN = number & { __notNaN: true };

Subsequently, you can create a validator for this type:

function notNaN(n: any): n is NotNaN {
  return !isNaN(n);
}

Ensure that your class properties are typed appropriately as well:

class Digit {
  constructor(public value: NotNaN) { }
}

This approach allows you to do things like the following:

let digit = new Digit(12 as NotNaN);
let someValue = +prompt("Surprise me!");

// Properly validated and working:
if(notNaN(someValue)) {
  digit.value = someValue;
}

// Without proper validation:
digit.value = someValue;
// TypeError: type 'number' is not assignable to type 'NotNaN'

By following this method, you can eliminate the need for getters/setters. Instead of throwing errors, it's important to validate inputs and operations accurately, which is exactly what this enforces.

Answer №2

There may not be a definitive answer to this question. In my opinion, it would be ideal to have a unified approach for setting the value, which can then be invoked from the constructor.

class NumberDigit {
  private _value: number = null;

  constructor(value: number) {
    this.setValue(value);
  }

  set value(newValue: number) {
    this.setValue(newValue);
  }

  get intValue() {
    return this._value;
  }

  private setValue(numValue: number) {
    if (isNaN(numValue)) { throw new Error('Invalid digit provided.'); }
    this._value = numValue;
  }

}

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

Stopping the papaparse streaming process once a certain number of results have been achieved

Currently, I am utilizing the PapaParse library to parse a large CSV file in chunk mode. During my data validation process, I encountered an issue where I was unable to stop streaming the data once validation failed. I attempted to halt the streaming by ...

Troubleshooting: AngularJS fails to refresh UI when using SignalR

When I attempt to incorporate this angularjs code into a signalR function, the UI does not update. Even using $scope.$apply() does not trigger a digest. This is the code snippet: var notificationHub = $.connection.progressNotificationHub; notificationHub. ...

Utilizing ReactJS onBlur event for email field validation

Currently, I am using a MaterialUI Textfield for email input validation upon leaving the field. To achieve this, I have implemented a function triggered when the onBlur event occurs. My intention is to display an error message using helpertext. Here' ...

Exploring the World with AngularJS and Google Maps API through ngGeolocation

Having some difficulty displaying my geolocation on Google Maps API using a marker. I am utilizing the ng controller ngGeolocation and the http://angular-ui.github.io/angular-google-maps/ Previously, I hardcoded the marker and map location without any is ...

Maximizing values entered into form fields

Looking for a way to extract the highest number from a set of input fields in an HTML form using JavaScript? Take this example: <input id="temp_<strong>0</strong>__Amount" name="temp[<strong>0</strong>].Amount" type="text" valu ...

jester: constantly jest navigator's mock & check against userAgent/vendor

Purpose: Need to iterate through different combinations of the userAgent Simulate navigator behavior Execute the test Observation: I simulated the navigator.userAgent, simulation works as planned, first test executes as expected Second simulation is per ...

What is the best way to retrieve a particular element from a dictionary?

In my dictionary structure, I have a list containing 2 dictionaries. Here is an example: dict: { "weather":[ {"id": 701, "main": "Mist", "description": "mist"}, {"id": 300, "main": "Drizzle", "description": "light intensity drizzle"} ] } If I wan ...

What is the best way to include default text in my HTML input text field?

Is it possible to include uneditable default text within an HTML input field? https://i.stack.imgur.com/eklro.png Below is the current snippet of my HTML code: <input type="text" class="form-control input-sm" name="guardian_officeno" placeholder="Off ...

Error: Unable to iterate over JSON data as json.forEach is not a valid function

let schoolData = { "name": "naam", "schools" : [ "silver stone" , "woodlands stone" , "patthar" ], "class" : 12 } schoolJSON = JSON.stringify(sc ...

Should a checkbox be added prior to the hyperlink?

html tags <ul class="navmore"> <li><a href="link-1">Link 1</a></li> <li><a href="link-2">Link 2</a></li> </ul> Jquery Implementation in the footer $(".navmore li a").each(function(){ v ...

Using NextJS: Issue with updating Value in useState

In my current project, I am attempting to display a string that changes when a button is pressed in my NextJs application. Here's the code snippet I am working with: 'use client' import { useState } from 'react' export default fu ...

Is there a way to customize CKEditor to prevent it from continuously adding <p></p> tags within the textarea automatically?

As I was going through the CKEditor tutorial, I implemented the following: $( '#editor' ).ckeditor(function(){}); //it's working great!! However, after submitting the form, I noticed that by default, the textarea displays <p></p ...

Encountering an undefined property error while trying to access index '0' in Angular 6 with Angular Material's mat-radio-group component, specifically when attempting to set a dynamic variable within

Currently, I am working with Angular 6 and Angular Material. My project involves a dynamic list of polls with various options. I am attempting to display the selected option using two-way data binding. However, due to the dynamic nature of my list, I have ...

What sets apart the placement of these two local variables?

Are there any distinguishable differences between the following two methods of defining local variables? Is it possible for the second method to inadvertently access global scope? I have noticed that multiple components within the application contain const ...

The rendering of the input dropdown control in Angular JS is experiencing difficulties

I am currently using your Angular JS Input Dropdown control, and I have followed the code example provided on your demo page in order to implement the control on a specific page of my website built with PHP Laravel. However, I have encountered an issue dur ...

Combining disparate arrays with serialized name/value pairs

Is there a way to merge an unassociated array with serialized name/value pairs without manually iterating over them? //First, I select certain values from mytable var data = $('#mytable input:checked'); console.log(data); //Object[input attribu ...

Leverage a nearby module with a local dependency

My current challenge involves integrating a local library into my project. I have been following two tutorials: how to create a library and how to consume a local library. Despite having a well-structured sample library with package.json and index.ts, I am ...

The continuous rerendering of my component occurs when I use a path parameter

In my project, I am working on utilizing a path parameter as an ID to fetch data for a specific entity. To accomplish this, I have developed a custom data fetching hook that triggers whenever there is a change in the passed parameters. For obtaining the bo ...

Updating state with new data in React: A step-by-step guide

Recently, I delved into the world of reactjs and embarked on a journey to fetch data from an API: constructor(){ super(); this.state = {data: false} this.nextProps ={}; axios.get('https://jsonplaceholder.typicode.com/posts') ...

JavaScript guide: Deleting query string arrays from a URL

Currently facing an issue when trying to remove query string arrays from the URL. The URL in question looks like this - In Chrome, it appears as follows - Var url = "http://mywebsite.com/innovation?agenda%5B%5D=4995&agenda%5B%5D=4993#ideaResult"; ...