typescript optional property with a getter
Asked Answered
S

10

17

This is a simplified example:

class PersonParms{
    name:string;
    lastName:string;
    age?:number;
    get fullName(){return this.name + " " + this.lastName;}
}

class Person{
    constructor(prms:PersonParms){
    }
}

new Person({name:'John',lastName:'Doe'})  // ts error: Property 'fullName' is missing in type '{ name: string; lastName: string; }'.

The idea is to pass a literal object as the intizalizer of PersonParms but having that getter you can neither declare the getter optional or add the property to the object literal. Is there another way to achieve it?

Sober answered 10/2, 2018 at 21:48 Comment(1)
Consider defining an interface interface IPersonParms { name:string; lastName:string; age?:number; readonly fullName?: string; }. Casting object literal to class doesn't seem to be useful - getter won't magically appear there anyway, you'll need to create an instance of a PersonParms class.Parlay
G
2

I found this solution which is ok for me:

class Person {
  name?:string;
  lastName?:string;
  age?: number;
  fullName?:string;

  constructor(public config: { name: string, lastName: string }) {
    Object.defineProperty(this,'fullName',{
           get(){return this.name + " " + this.lastName;}
          });

}
Grubbs answered 24/11, 2018 at 6:30 Comment(1)
This worked for my case as well. I am deprecating a property address in favor of addressLine1, but while callers switch over, I need to support both. I wanted a way to set addressLine1 to the same value of address if address was set by the caller. tsplay.dev/NdElMmFarm
G
13

Very interesting. I think, you should report an issue to TypeScript, because methods can be optional (see below), but property getters not. It is strange.. As a workaround I can suggest two variants. A nice one:

class PersonParms {
    name:string;
    lastName:string;
    age?: number;

    getFullName?() {return this.name + " "+this.lastName;}
}

And a second one, that is hacky, because there we make all the properties optional when passing to constructor.

class PersonParms {
    name:string;
    lastName:string;
    age?: number;

    get fullName(){return this.name + " "+this.lastName;}
}

class Person{
    constructor(prms: Partial<PersonParms>){
    }
}
Gitagitel answered 11/2, 2018 at 8:3 Comment(1)
I second that. It is quite misleading to have to assign a value to a property that's (intentionally) read-only in the initialization - and that's effectively where the case described here takes us.Gladiolus
N
6

As of April 2020, there is NO way to implement this.

There is an inconclusive PR for this: https://github.com/microsoft/TypeScript/pull/16344

A proposed solution via an interface is presented here: https://github.com/microsoft/TypeScript/pull/16344

Personally, the solution did not meet my needs, and I rather declared the property as private.

Hopefully, we can have better luck in the future.

Note answered 27/4, 2020 at 18:53 Comment(1)
Your comment saved me hours of searching for a solution that doesn't exist. I felt there is no possible way this was oversight, must just be me not understanding some typescript syntax. But nope... this essential, fundamental thing was indeed NOT implemented, and now typescript is preventing me from doing what should be possible. Awesome. Setting to private is not a work-around, and I haven't found one yet.Delative
T
2

Is there another way to achieve it?

Here is how I would do it:

class Person {
  constructor(public config: { name: string, lastName: string }) {}
  age?: number;
  get fullName() { return this.config.name + " " + this.config.lastName; }
}

new Person({ name: 'John', lastName: 'Doe' }) 
Tricorn answered 10/2, 2018 at 21:58 Comment(1)
Yes but I will use PersonParms as base in several other classes so the optional getter should be in PersonParns to not have to repeat it in all the subclasses.Sober
G
2

I found this solution which is ok for me:

class Person {
  name?:string;
  lastName?:string;
  age?: number;
  fullName?:string;

  constructor(public config: { name: string, lastName: string }) {
    Object.defineProperty(this,'fullName',{
           get(){return this.name + " " + this.lastName;}
          });

}
Grubbs answered 24/11, 2018 at 6:30 Comment(1)
This worked for my case as well. I am deprecating a property address in favor of addressLine1, but while callers switch over, I need to support both. I wanted a way to set addressLine1 to the same value of address if address was set by the caller. tsplay.dev/NdElMmFarm
S
1

If you creates a new instance of PersonParms then the error will be gone.

class PersonParms{
    name:string;
    lastName:string;
    age?:number;
    get fullName(){return this.name + " "+this.lastName;}
}

class Person{
    constructor(prms:PersonParms){
    }
}

const personParams = new PersonParms();
personParams.name = 'John';
personParams.lastName = 'John';
new Person(personParams)  // No error because this is an instance of PersonParams

I am not sure where/how do you use PersonParms.fullname but in your case I would use this:

interface PersonParms{
    name:string;
    lastName:string;
    age?:number;    
}

class Person implements PersonParms{
    name: string;
    lastName: string;
    age?:number
    constructor(prms: PersonParms) {
        this.name = prms.name;
        this.lastName = prms.lastName;
        this.age = prms.age;
    }

    get fullName(){return this.name + " "+this.lastName;}
}

const person = new Person({ name: 'John', lastName: 'Doe' });

console.log(person.fullName); // John Doe
Slavery answered 19/1, 2019 at 20:52 Comment(0)
G
1

Remember that optional is a type concept. A getter is an implementation. The implementation can return an optional type. In a class that implements an interface with an optional readonly property, the class may leave off the getter. See this answer get and set in TypeScript

Goetz answered 10/7, 2021 at 17:20 Comment(0)
S
0
class PersonParms {
  name: string;
  lastName: string;
  age?: number;
  fullName?: string = this.name + ' ' + this.lastName;
}

class Person {
  constructor(prms: PersonParms) {
  }
}

new Person({ name: 'John', lastName: 'Doe' });
Sisley answered 24/11, 2018 at 6:51 Comment(3)
Hi Akshay, instead of just showing the code you should also explain what you've changed and why it solves the problem.Caught
Here I made only one change. fullName?: string = this.name + ' ' + this.lastName;Sisley
It is set in constructor. Is not alternative to getter setterComprehensible
L
0

If you cast the object this will prevent the compile time error.

export class IndividualModel {
    constructor(individual: IndividualModel = null) {
        if (individual) {
            this.individualKey = individual.individualKey;
            this.firstName = individual.firstName;
            this.lastName = individual.lastName;
        }
    }

    individualKey: string;
    firstName?: string;
    lastName?: string;
    get fullName(): string {
        return `${this.lastName}, ${this.firstName}`;
    }
}
const individual = new IndividualModel(<IndividualModel>{ individualKey: 'some-key' });
Leifeste answered 29/9, 2020 at 21:18 Comment(0)
J
0

If you don't want to make every property optional using Partial, you can just omit the getters.

class PersonParms{
        name:string;
        lastName:string;
        age?:number;
        get fullName(){return this.name + " " + this.lastName;}
    }
    
    class Person{
        constructor(prms: Omit<PersonParms, 'fullName'> ){
        }
    }
    
    new Person({name:'John',lastName:'Doe'})
Julejulee answered 25/12, 2022 at 23:11 Comment(0)
S
-3
   class PersonParms {
      name: string;
      lastName: string;
      age?: number;

     getFullName?(): string | null { return this.name + ' ' + this.lastName; }
  }

  class Person {
     constructor(prms: PersonParms) {
     }
  }
 new Person({ name: 'John', lastName: 'Doe' });
Sisley answered 24/11, 2018 at 7:39 Comment(1)
getFullName?() is not a valid typescript syntaxGambill

© 2022 - 2024 — McMap. All rights reserved.