Skip to content

Optional class properties #5421

Description

It would be useful to be able to declare optional properties on a class, similar to how it works for interfaces. This would allow more expressive structural subtyping with plain classes:

class Name {
  first: string;
  middle?: string;
  last: string;
}

function printName(name: Name) {
  //...
}

printName({first: "First", last: "Last" });

Technically, this is already possible with the recent change to allow merging interfaces and non-ambient classes:

interface Name {
  middle?: string;
}

class Name { 
  first: string;
  last: string
}

However, it's not very DRY for this use case. Now, you might ask, why not just use an interface and not a class when dealing with plain objects with optional properties? Because interfaces are not real values, which means that they cannot be passed around or be decorated, both of which are often useful.

My motivation for requesting this comes from writing a wrapper library for Immutable.js. I'm trying to create a nice way for library users to declare data items and update them. Here's an abbreviated example of how I would like it to look:

class TodoData {
  @prop text?: string;
  @prop completed?: boolean;
}

class Todo extends TypedMap(TodoData) { }

// todoMap is a typed map representing a Todo
var todo = Todo.create();

// The optional properties allow a nice typed syntax for updating the map
todoMap = todoMap.set({text: "Hello"});

// The @prop decorator was used to run Object.defineProperty on each declared property
console.log(todoMap.text);

Activity

  1. mhegazy commented on Oct 27, 2015

    @mhegazy
    Contributor

    Looks like what you care about is the typing for the get and set methods, rather than really having an optional property on the class. from your example the property will exist. if so, then this looks like #1295, you want to type set as:

    interface TypedMap<T> {
        set(name: string, value: T[name]): void;
    }
  2. russpowers commented on Oct 28, 2015

    @russpowers
    Author

    Thanks for pointing that out, #1295 is an interesting idea. But it would only handle a simple set/get, right? What about something like a typed deep merge:

    someObject.merge({child: {name: "Child" }, optional: false });

    Anyways, I think there's a better motivating example -- validation of structurally subtyped data using decorators:

    class User {
      @maxLength(50)
      firstName: string;
    
      @maxLength(50)
      middleName?: string;
    
      @maxLength(50)
      lastName: string;
    
      @email
      emailAddress: string;
    
      @phone
      phone?: string;
    
      @phone
      fax?: string;
    }
    
    function createUser(user: User) {
      // run validations and add to user list
    }
    
    createUser({
      firstName: "First",
      lastName: "Last",
      email: "first.last@gmail.com",
      phone: "123-345-4567"
    });

    This is only possible currently if there are no optional properties in your data, but often that's not the case. Also, if we could somehow get isOptional as a parameter to the decorator, we could use it in the validation as well. We could then have some sort of generic validation/schema checking function for these decorated classes:

    function validate<T>(ctor: { new(): T }, data: any): T {
      // validate data and return typed object if successful, throw if not
      // or maybe return a list of errors
    }
    
    var untypedUserData: any = fetchUserDataFromWebService();
    var typedUser = validate(User, untypedUserData);
  3. mhegazy commented on Oct 28, 2015

    @mhegazy
    Contributor

    looks like a duplicate of #4889 then.

  4. russpowers commented on Oct 28, 2015

    @russpowers
    Author

    Yes, the partial types would be perfect for the Immutable.js use case. I don't think they would handle the validation use case, since you don't want everything to be optional.

  5. CreepGin commented on Jun 3, 2016

    @CreepGin

    Optional class members is implemented in #8625

  6. locked and limited conversation to collaborators on Jun 19, 2018
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Metadata

Metadata

Assignees

No one assigned

    Labels

    DuplicateAn existing issue was already created

    Type

    No type

    Projects

    No projects

      Milestone

      No milestone

      Relationships

      None yet

      Development

      No branches or pull requests

      Issue actions