The Practical Client

Overloading TypeScript Functions

TypeScript has some "interesting" limitations when it comes to overloading functions. But it also offers you some options when you need more flexibility in declaring functions, including both optional parameters and infinite parameter lists.

One note before I begin: I'm using Visual Studio 2015 Community Edition for this column. (It's free!)

Obviously, by letting you specify the data types for the parameters you pass to your methods, TypeScript lets you be more specific with your parameters than plain-old JavaScript. However, because TypeScript is eventually compiled into JavaScript, there's at least one place where TypeScript is less flexible than the languages you're used to working with on the server.

Overloading Limits
In many languages you can have multiple methods with the same name, provided those various versions of the method all have different parameter lists. Those parameter lists can be different in two ways: They can have a different number of parameters or the parameter in a specific position can have a different type. For example, these are all acceptable overloads in C# for methods that share the name GetFirstName:

public void GetFirstName(string fullName)
public void GetFirstName(string fullName, int pos)
public void GetFirstName(Customer fullName)

TypeScript supports the first two overloading examples because the two GetFirstName methods have a different number of parameters.

However, when TypeScript is compiled to JavaScript, all of the datatype information is stripped away. Both of my first and last name GetFirstName methods have exactly one parameter and their parameter lists only differ in data type (string vs. Customer). If the data type is ignored, as is the case in JavaScript, then the two methods are identical and it's impossible, at run time, to choose between them.

TypeScript will still let you do a version of overloading for my last two examples if you're willing to stretch the definition of "overloading." To support my first and last examples, you first need to write two GetFirstName method declarations, one accepting a string parameter and one accepting a Customer parameter. This code does that:

function GetFirstName(name: string): string;
function GetFirstName(cust: Customer): string;

You then write one GetFirstName method that accepts a single parameter of either string or Customer:

function GetFirstName(stringOrCustomer: string | Customer): string
{

Inside the method, you can check the type of the parameter (string or Customer) and decide what to do:

if (stringOrCustomer && typeof stringOrCustomer === "string") 
  {
    return stringOrCustomer.substr(0, stringOrCustomer.indexOf(" "));
  }
else 
  {
    return stringOrCustomer.Name.substr(0, stringOrCustomer.Name.indexOf(" "));
  }

This is many things but, personally, I wouldn't call it overloading (though you do get IntelliSense support and type checking for the various method versions). It does, however, work.

One note: If you read my earlier column where I discussed the arrow syntax for declaring functions, be aware that you can't use that syntax when declaring an "overloaded" function.

Optional Parameters
An alternative to overloading is to define a single function with optional parameters. In JavaScript, of course, all parameters are optional: If you have a function that accepts three parameters and you pass none (or five), it's not a problem. On the other hand, in TypeScript all parameters are required by default: If your method accepts three parameters, your code won't compile unless you pass exactly three parameters to that method.

You can override that default, though, and tell TypeScript that a parameter is optional. In fact, TypeScript provides two different ways of indicating that a parameter is optional. I can, if I wish, specify that a parameter is optional by adding a question mark (?) to the parameter's name. This GetFirstName method has its only parameter marked as optional:

function GetFirstName(custName?: string): string
{
  return custName.substr(0, custName.indexOf(" "));
};

However, I'm not fond of using this syntax for specifying that a parameter is optional for two reasons: I work in several different languages and I have a terrible memory. As a result, I'm never sure what this particular language will do when an optional parameter is omitted. I can't remember, for example, if in TypeScript the string custName will be set to null or a zero length string when the parameter is omitted.

Instead, I prefer TypeScript's other syntax: I can flag a parameter as optional by providing a default value for it. This example still makes my custName parameter optional, but provides a value for the parameter (null, in this case) when the calling code omits the parameter:

function GetFirstName(custName: string = null): string
{
  return custName.substr(0, custName.indexOf(" "));
};

Of course, I've just moved my problem with the missing parameter around but haven't actually solved it: If my method isn't passed a string, it's going to blow up when the code inside the method tries to manipulate my default null value. In my code, I need to check for my default value and decide what I'm going to do about it. This example checks for the default value in the custName parameter and returns a zero length string when that happens:

function FirstName(custName: string = null): string
{
  if (custName == null) 
  {
    return "";
  }
  else 
  {
    return custName.substr(0, custName.indexOf(" "));
  }
};

As in other languages that support optional parameters, any optional parameters must follow any required parameters, regardless of which syntax you use to specify that a parameter is optional.

Infinite Parameters
I can also specify that my function will accept an infinite number of parameters that are to be gathered into an array. To do that, I add an ellipsis (…) to the start of my parameter name. The version of my GetFirstName function shown in Listing 1 accepts zero or more strings in an array called custNames. Inside the function, I process the array using a forEach loop.

Listing 1: GetFirstName Function with Infinite Parameters
function GetFirstName(...custNames: string[]): Array<string>
{
  var names: Array<string>;
  names = new Array<string>();
  if (custNames.length > 0)
  {
    custames.forEach(function (custName: string) 
    {
      names.push(custName.substr(0, custName.indexOf(" ")));
    });
    return names;
  }
  else
  {
    return null;
  }
};

A parameter prefixed with an ellipsis must either be the only parameter for the function or the last parameter for the function. When used as the last parameter, these ellipsis parameters gather up all of the values not accepted by the preceding required parameters. These kinds of parameters are, therefore, referred to as "rest" parameters because they get "the rest of the values."

As you can see, you've got quite a lot of flexibility in defining methods in TypeScript: overloading (mostly), a workaround for what TypeScript doesn't handle well in overloading, optional parameters, and infinite parameters. You should be able to do whatever you want and get both IntelliSense support and compile time checks.

About the Author

Peter Vogel is a system architect and principal in PH&V Information Services. PH&V provides full-stack consulting from UX design through object modeling to database design. Peter tweets about his VSM columns with the hashtag #vogelarticles. His blog posts on user experience design can be found at http://blog.learningtree.com/tag/ui/.

comments powered by Disqus

Featured

Subscribe on YouTube