0

I have the following code:

class MyClass {
    name: string = "myname";
    constructor(public action: string) { }
}

let obj1: MyClass = { action: "act1" };

It doesn't compile. The error on the last line is:

Property 'name' is missing in type '{ action: string; }' but required in type 'MyClass'.

I was expecting that, because 'name' property has a default value, I would not be expected to specify it for obj1. But it's not the case. Also, it seems the last line does not even call the ctor, so it's not a ctor syntactic sugar but rather a completely different initialization call. Is there a way I can set defaults for some properties and avoid initializing them in this technique?

3
  • 1
    You might have to set the name parameter to be optional : name?:string = "myname". Commented Oct 23, 2019 at 14:48
  • 3
    That's not JSON. That's an object literal. JSON is a text format. Commented Oct 23, 2019 at 14:48
  • 3
    Don't use classes for this. Use interfaces. Commented Oct 23, 2019 at 14:50

3 Answers 3

3

When you write:

let obj1: MyClass = { action: "act1" };

You are using the class MyClass as a type, in the sense that you're telling the compiler that the variable obj1 complies with the MyClass type. It has to have all the mandatory fields of the class and cannot have any not known properties.

This is nice because if the type would have 200 properties, the intellisense could hint you about missing properties, for instance.

Now, MyClass is actually a class, so it does not only work as a type/interface. It also has some functionality, i.e. you can invoke the constructor and you can use extends when declaring the class.

Now, default properties of a class aren't default properties for the type. If you try to add a initializer in a typescript interface or type, you will see an error; instead, when you add a default property to a class, it means that an instance will have that property initialized to the default value only when called from the constructor.

So, there are some things you can do to fix the issue:

  1. You may continue using MyClass as a type in that scenario, but then you need to still declare the name prop in the object literal:

let obj1: MyClass = {action: "act1", name: "first"};

If you do so, you still will face a:

Object literal may only specify known properties

Because the action property does not exist in MyClass. You would need to add an action property to MyClass.

  1. Use a constructor

let obj1 = new MyClass("act1");

While you can in some scenarios have a valid reason to use classes as types/interfaces, you should probably call the class constructor when possible -like in this example-, or, as a comment suggest, use an interface for this.

In this case, I think you should prefer to call the constructor.

  1. If you only need the type to get compiler checks/intellisense on the variable type, you can use an interface:
interface MyCustomType {
  name: string;
  action?: string;
}

So you get a warning if you try to type a variable as MyCustomType without the name:

let obj: MyCustomType = {
  action: 'act1',
} // error, you need to set 'name' since it is a mandatory field

You can set an object with or without action, since it is optional (due to the '?' in the declaration).

You cannot add unknown fields:

let obj: MyCustomType = {
  name: 'name',
  value: 1, // error, unknown field
}
Sign up to request clarification or add additional context in comments.

3 Comments

Not sure I follow option #2. Calling new MyClass({action:"a"}) doesnt seem to work. But calling MyClass("a") does. I am specifically looking for the former because I need a way to get a compiler error in case I forget a NAMED argument.
yeah the constructor only takes a string parameter, I made a mistake with the param. If you're not using the constructor, and only want intellisense about forgotten fields, it is better to use an interface. I'll edit my answer
Thanks, tried that, but again, the problem is that I can't set any defaults that aren't coming from the object literal "initializer". I finally came up with a different solution which combines both approaches at a tradeoff, I've posted the answer below.
0

In your code let obj1: MyClass = { action: "act1" };

You are not initializing obj1 to be an instance of MyClass, but instead assigning an object { action: "act1" } to a variable supposed to have type MyClass. So the typescript error arise.

Correct implementation:

class MyClass {
    name: string = "myname";
    constructor(public action: string) { 
    }
}

let obj1 = new MyClass("action");
console.log(obj1);

/* 
   outputs:

   {
      action: "action"
      name: "myname"
   }
*/

Comments

0

Thanks for all the suggestions above, I've finally come up with a solution that lets me use the ctor and still get errors of any missing/extra values AND letting me set default values that are optional in the object literal "initializer":

class MyClassValues {
    constructor(public name: string, public action?: string) {}
}

class MyClass extends MyClassValues {
    constructor(v: MyClassValues) {
        super(v.name, "default1");
    }
}

let c1 : MyClass = new MyClass({}); // error, missing name
let c2 : MyClass = new MyClass({x:"y"}); // error, unknown prop
let c3 : MyClass = new MyClass({name:"hello"}); // no error
console.log(c3.action); // prints "default1";

Comments

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.