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?

tru7
  • 6,348
  • 5
  • 35
  • 59
  • 2
    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. – Aleksey L. Feb 11 '18 at 06:47

10 Answers10

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>){
    }
}
Pavel
  • 2,602
  • 1
  • 27
  • 34
  • 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. – Wojtek Jul 19 '18 at 19:21
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.

Santiago M. Quintero
  • 1,237
  • 15
  • 22
  • 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. – dapug Oct 14 '21 at 21:55
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' }) 
basarat
  • 261,912
  • 58
  • 460
  • 511
  • 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. – tru7 Feb 10 '18 at 23:00
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;}
          });

}
Bogdan
  • 656
  • 15
  • 25
  • 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. https://tsplay.dev/NdElMm – uPaymeiFixit Mar 23 '23 at 05:09
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
distante
  • 6,438
  • 6
  • 48
  • 90
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

Ezward
  • 17,327
  • 6
  • 24
  • 32
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' });
Akshay
  • 52
  • 2
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' });
mtsuggs
  • 91
  • 5
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'})
-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' });
Akshay
  • 52
  • 2