Home > Net >  How to initialize a readonly/private value in typescript using a setter?
How to initialize a readonly/private value in typescript using a setter?

Time:10-12

Some background for the code in question: I'm trying to implement a configuration class that's convenient to initialize for the user. For this purpose, my configuration takes an object implementing configuration interface as a constructor argument, while the configuration class handles things like default values, validation etc.

import Uri from '../Uri'

export default interface BaseUriConfigurationInterface
{
    uri: Uri | string
    mergePath?: boolean
    mergeQuery?: boolean
    defaultScheme?: 'http' | 'https' | null
    overrideScheme?: boolean
};

In the configuration, uri is the only configuration option without a default value.

import ConfigurationInterface from '../contract/BaseUriConfigurationInterface'
import Uri from '../Uri'
import UriSyntaxError from '../error/UriSyntaxError'
import ConfigurationError from '../error/ConfigurationError'

export default class BaseUriConfiguration implements ConfigurationInterface
{
    private _uri: Uri
    readonly mergePath: boolean
    readonly mergeQuery: boolean
    readonly defaultScheme: 'http' | 'https' | null
    readonly overrideScheme: boolean

    constructor(config : ConfigurationInterface)
    {
        this.mergePath = true
        this.mergeQuery = false
        this.defaultScheme = null
        this.overrideScheme = false

        Object.assign(this, config);
    }

    public get uri() : Uri
    {
        return this._uri
    }

    private set uri(uri: Uri | string)
    {
        if(typeof uri === 'string') {

            try {
                uri = new Uri(uri)
            }
            catch(error) {
                if(! (error instanceof UriSyntaxError)) {
                    throw error
                }

                const msg = `Invalid base uri given. See previous error for more details.`
                throw new ConfigurationError(msg, error)
            }
        }

        this._uri = uri
    }
}

The idea here is that the constructor sets all the default values for configuration options, while setters are used to mutate and validate data. In this case for example, it might be more convenient for the user to simply supply a string instead of initializing a new uri object, thus the setter allows for an argument of either type. I'm using Object.assign to quickly override any default values with the ones supplied by the user.

The configuration options are readonly, hence the setter is private: it should only be used once to initialize the value. This works since typescript 4.3, which allowed different visibility for getters and setters.

The Problem:

Now here's the problem: Property '_uri' has no initializer and is not definitely assigned in the constructor. - I'm getting this error because typescript can't tell that the interface guarantees that the uri is initialized in the constructor using Object.assign

I tried fixing this by manually adding this.uri = config.uri in the constructor, but that doesn't work either since I for some reason get the following error: Type 'string | Uri' is not assignable to type 'Uri'.

The Question:

In short, how do I initialize a readonly/private value in typescript using a setter? Or if this is not possible, what sort of an alternate approach should I use for my configuration structure?

Possible Solution:

I suppose I could integrate the setter code directly into the constructor, but that approach seems far from ideal because of the following reasons:

  1. If you have a lot of code across multiple setters, the constructor will get cluttered very fast.
  2. That would not work with Object.assign, forcing you to manually set every configuration option, which would be very verbose and tedious.

CodePudding user response:

I'm getting the error '_uri' has no initializer and is not definitely assigned in the constructor. because typescript can't tell that the interface guarantees that the uri is initialized in the constructor using Object.assign

In that case, you can suppress the error by writing

private _uri!: URL
//          ^

in your class.

That said, I'd recommend not to use setters/getters for this, rather write a method private static validUri(uri: Uri | string): Uri and use it like this.uri = BaseUriConfiguration.validUri(config.uri); in the constructor.

  • Related