Setting default value for TypeScript object passed as argument
Asked Answered
G

8

399
function sayName(params: {firstName: string; lastName?: string}) {
    params.lastName = params.lastName || 'smith';  // <<-- any better alternative to this?
    var name = params.firstName + params.lastName
    alert(name);
}

sayName({firstName: 'bob'});

I had imagined something like this might work:

function sayName(params: {firstName: string; lastName: string = 'smith'}) {

Obviously if these were plain arguments you could do it with:

function sayName(firstName: string, lastName = 'smith') {
    var name = firstName + lastName;
    alert(name);
}

sayName('bob');

And in coffeescript you have access to the conditional existence operator so can do:

param.lastName ?= 'smith'

Which compiles to the javascript:

if (param.lastName == null) {
    param.lastName = 'smith';
}
Galarza answered 26/4, 2014 at 18:17 Comment(6)
Your suggested solution params.lastName = params.lastName || 'smith'; is actually rather fine - it handles empty strings, undefined strings and null values.Chenoweth
@SteveFenton And anything falsy. It's fine in the case of a last name, but generally not a good idea. That's why Typescript translates default values to if(typeof x === "undefined") { … }. Not that you don't know that, but just pointing out the general case for the OP.Pinchas
@IngoBürk true, but due to lastName?: string it can only ever be as SteveFenton said "handles empty strings, undefined strings and null values".Galarza
@Galarza Within Typescript, yes. But that's just compile-time. Either way, like I said, just pointing out a general case scenario :)Pinchas
Agree with steve here. Quite commonly it is better to have a single config argument instead of e.g. 10 arguments. In this case params.lastName = params.lastName || 'smith'; is the pattern I useInhumanity
typescripttutorial.net/typescript-tutorial/… param1: type = defaultValueCrinkleroot
B
446

Actually, there appears to now be a simple way. The following code works in TypeScript 1.5:

function sayName({ first, last = 'Smith' }: {first: string; last?: string }): void {
  const name = first + ' ' + last;
  console.log(name);
}

sayName({ first: 'Bob' });

The trick is to first put in brackets what keys you want to pick from the argument object, with key=value for any defaults. Follow that with the : and a type declaration.

This is a little different than what you were trying to do, because instead of having an intact params object, you have instead have dereferenced variables.

If you want to make it optional to pass anything to the function, add a ? for all keys in the type, and add a default of ={} after the type declaration:

function sayName({first='Bob',last='Smith'}: {first?: string; last?: string}={}){
    var name = first + " " + last;
    alert(name);
}

sayName();
Basifixed answered 15/9, 2015 at 21:45 Comment(13)
That solution makes for an interetsting code-hinting/intellisense feature as it shows the parameters fully destructured. I wouldn't expect to see that style in a settings object with more than a few parameters like in the example.Gorga
Is is also possible to rewrite this solution to use a interface/class like "sayNameSettings" instead?Fiord
While this works, destructuring the arguments this way makes it hard to follow IMO.Kursh
you can use 'as' keyword (in my opinion it's more readable), like {first='Bob',last='Smith'} as {first?: string; last?: string} but ideally you should create an interface for handle this cases...Tecu
You can also use the Partial utility types to the type part in the argument, if you have a pre-defined interface/type for the argumentBlesbok
Is there a way to pass the options object to another function?Perren
I think it's much easier & more readable to define the type: export type NameBits = { first?: string; last?: string; }; You can determine what's optional by deciding what to give a default value in the assignment. The optional flags ? in the type def make it passable to declare an object missing those properties of that type. This way you can pass the argument object by name. function sayName( nameBits: NameBits = { first: "Bob", Last: "Smith" } ) { /* Destructure named vars if you want */ const { first, last } = nameBits; alert(`${first} ${last}` ); }Wame
Unfortunately using function sayName( nameBits: NameBits = { first: "Bob", Last: "Smith" } ) and calling it with sayName({ first: "Alice" }) results in nameBits being { first: "Alice" }. i.e. Last is not set to the default value of SmithGeneralize
Why you need a ={} when you already have first='Bob' and last='Smith'?Hayott
@Rainning It's kind of tricky, but that's the default value of the whole parameter object. It allows you to call the function without providing any arguments. Because that's there, you could call this function like so: sayName(). If you remove the ={}, you'd have to at least pass in an empty object.Clercq
@DanielKaplan: Thanks for the explanation. Wow but I still think that requiring passing a {} is kind of an ugly design.Hayott
The readability of this sucks.Pedestal
I have to look up this syntax every time I have this use case.Clercq
A
81

Object destructuring the parameter object is what many of the answers above are aiming for and Typescript now has the methods in place to make it much easier to read and intuitively understand.

Destructuring Basics: By destructuring an object, you can choose properties from an object by key name. You can define as few or as many of the properties you like, and default values are set by a basic syntax of let {key = default} = object.

let {firstName, lastName = 'Smith'} = myParamsObject;

//Compiles to:
var firstName = myParamsObject.firstName, 
_a = myParamsObject.lastName, 
lastName = _a === void 0 ? 'Smith' : _a;

Writing an interface, type or class for the parameter object improves legibility.

type FullName = {
  firstName: string;
   
  /** @defaultValue 'Smith' */
  lastName ? : string;
}

function sayName(params: FullName) {

  // Set defaults for parameter object
  var { firstName, lastName = 'Smith'} = params;

  // Do Stuff
  var name = firstName + " " + lastName;
  alert(name);
}

// Use it
sayName({
  firstName: 'Bob'
});
Associative answered 24/8, 2016 at 3:43 Comment(4)
This is so much better than the accepted answer. Destruction inside the function body makes more sense than in the paramter itselfDanitadaniyal
@DollarAkshay: why does it make more sense to hide in the function body what the default value of a function parameter is?Dustidustie
Just an FYI as I came across this, if you're wanting to have all of the properties as optional, you must provide a default blank object i.e. function sayName(params: FullName = {})Abode
Yes Emobe, though some versions of TypeScript may complain about type of {} You can fix this by adding a as FullName typecast to your empty object like this: function sayName(params: FullName = {} as Fullname)Associative
R
81

Typescript supports default parameters now:

https://www.typescriptlang.org/docs/handbook/functions.html

Also, adding a default value allows you to omit the type declaration, because it can be inferred from the default value:

function sayName(firstName: string, lastName = "Smith") {
  const name = firstName + ' ' + lastName;
  alert(name);
}

sayName('Bob');
Redivivus answered 6/7, 2017 at 0:0 Comment(4)
How does this apply to the OP's question regarding default value for TypeScript object passed as an argument? You should show an example with an object argument.Luci
It doesn't, but I found this question when I was searching for how to provide default parameters in typescript in general, and none of the answers addressed that. I also posted this response before I noticed the subtle "object" param clarification.Redivivus
this doesn't apply to the question as this is already mentioned in there. OP is asking for default value in JSONSacerdotal
this should be top answer. thank you.Riccardo
G
31

No, TypeScript doesn't have a natural way of setting defaults for properties of an object defined like that where one has a default and the other does not. You could define a richer structure:

class Name {
    constructor(public first : string, 
        public last: string = "Smith") {

    }
}

And use that in place of the inline type definition.

function sayName(name: Name) {
    alert(name.first + " " + name.last);
}

You can't do something like this unfortunately:

function sayName(name : { first: string; last?:string } 
       /* and then assign a default object matching the signature */  
       = { first: null, last: 'Smith' }) {

} 

As it would only set the default if name was undefined.

Gorga answered 26/4, 2014 at 19:15 Comment(2)
I think this is the best answer to this question and should be marked as the correct answer.Nucleo
For others, this is no longer true. See @Benson's answerClercq
C
27

This can be a nice way to do it that does not involve long constructors

class Person {
    firstName?: string = 'Bob';
    lastName?: string = 'Smith';

    // Pass in this class as the required params
    constructor(params: Person) {
        // object.assign will overwrite defaults if params exist
        Object.assign(this, params)
    }
}

// you can still use the typing 
function sayName(params: Person){ 
    let name = params.firstName + params.lastName
    alert(name)
}

// you do have to call new but for my use case this felt better
sayName(new Person({firstName: 'Gordon'}))
sayName(new Person({lastName: 'Thomas'}))
Coccus answered 30/10, 2016 at 17:43 Comment(4)
@PWKad the defaults are set on the second and third line, "Bob" and "Smith"Coccus
I know this is an old answer, but a possible problem with this solution is that you can pass these arguments with null or undefined value because the Person class allows that. So it's syntactically valid to write sayName(new Person({firstName: undefined })); while it might not make any sense semantically.Hangchow
@Hangchow Could you elaborate on why this might be an issue? I'm pretty new to all this, and this solution seems the nicest option to me, insofar as it allows one to set defaults for the individual keys, as well as a default for the params object, itself; however, I want to be sure I understand the possible consequences of going this direction. Any insight would be appreciated.Ursi
This is a really good start to support some nicer syntax for dataclasses. To avoid changing semantics of the class just to support the constructor as @Hangchow mentioned, I have come up with a few solutions that you can check out here gist.github.com/d-bucur/c717d7fe0e78a635b7bf7960f48098e0Wristlet
G
5

Here is something to try, using interface and destructuring with default values. Please note that "lastName" is optional.

interface IName {
  firstName: string
  lastName?: string
}

function sayName(params: IName) {
  const { firstName, lastName = "Smith" } = params
  const fullName = `${firstName} ${lastName}`

  console.log("FullName-> ", fullName)
}

sayName({ firstName: "Bob" })
Gasper answered 10/12, 2019 at 7:53 Comment(0)
B
2

There is another way without destructuring could be to set the default value with the type like this:

function name(param1:type=defaultValue1, paramr2:type=defaultvalue2,...) {//}

An example could be

function addTwoNumbers(first:number = 1, second:number = 3):number {return first+second}
console.log(addTwoNumbers())
Bernitabernj answered 19/10, 2022 at 19:12 Comment(2)
Yep! However the benefit of having a function with a single argument that is an object is that the implementation here would not be able to type check if you give the arguments in the wrong order. For example: function divideTwoNumbers(numerator: number=1, denominator: number=2) { return numerator / denominator } would give the wrong result of 5 instead of 0.1 but not raise a type error if you invoked it like: const denominator = 10; divideTwoNumbers(denominator). Secondly to invoke it and use the default is not elegant/easy: divideTwoNumbers(undefined, denominator)Galarza
Unless I'm misunderstanding, this answers the question by changing it. This is passing in individual parameters instead of an object.Clercq
B
1

Without destructuring, you can create a defaults params and pass it in

interface Name {
   firstName: string;
   lastName: string;
}

export const defaultName extends Omit<Name, 'firstName'> {
    lastName: 'Smith'
}

sayName({ ...defaultName, firstName: 'Bob' })
Bein answered 6/2, 2020 at 2:6 Comment(1)
The above code does not compile.Clercq

© 2022 - 2024 — McMap. All rights reserved.