The Practical Client

Implementing Strategy Pattern and Callbacks in TypeScript

The TypeScript datatyping support not only applies to simple variables, it also supports you when doing clever things with functions. Specifically, TypeScript ensures you create reliable code when implementing callback functions.

In last month's column, I walked through the two syntaxes for defining "function datatypes" in TypeScript. I showed the JavaScript-like syntax, which looks like this:

var FirstName: (cust: Customer) => string;
FirstName =
  function (cust: Customer): string 
  {
    return cust.name.substr(0, cust.name.indexOf(" "));
  };

And the terser "fat arrow" syntax, which looks like this:

var FirstName: (custParm: Customer) => string =
  cust  => cust.name.substr(0, cust.name.indexOf(" ")) ;

Both of those sets of code do the same thing: They define a variable called FirstName to hold a function that accepts a Customer object and returns a string. The code then stores a function compatible with that declaration in the variable.

Regardless of how the function is declared, I would use the same code to call it:

var fname: string;
fname = FirstName(cust);

As I discussed in last month's column, datatyping your functions gives you better compile time checking then you'll get with JavaScript (and you'll also get better IntelliSense support as you type your code).

But those benefits really start paying off when you start doing clever things with functions: when, for example, you create functions that accept other functions as parameters (as when implementing the strategy pattern) or, especially, when you create a function that returns another function (as when creating a callback). In fact, if you use the fat arrow syntax when declaring your callback function, you'll eliminate a common JavaScript problem when working with callbacks.

Implementing the Strategy Pattern
The strategy pattern allows developers to control the processing performed by a function by providing another function that's used inside the function being called (it's a way of making the function being called more flexible). You've run across the strategy pattern in other languages whenever you've passed a lambda expression to a method.

If you want to have a function accept another function as a parameter, you simply declare a parameter using a function declaration. Developers can then pass a compatible function to your parameter (I'll call the function being passed to the parameter the "strategy function" to distinguish it from the function to which it's being passed).

This example, using the JavaScript-like syntax, defines a function called GetNamePart that returns a string; the GetNamePart function also expects to be passed a Customer object (in a parameter called cust) and a strategy function (in a parameter called nameProcessor). That nameProcessor strategy function, in turn, also returns a string and expects to be passed a customer object (called newCust). Inside GetNamePart, I call the strategy function, pass it a Customer object, and return the result:

function GetNamePart(cust: Customer, nameProcessor: (cust: Customer) => string): string
{
  return nameProcessor(cust);
}

Using the fat arrow syntax, I'd declare GetNamePart like this:

var GetNamePart: (cust: Customer, nameProcessor: (newCust: Customer) => string) 
                  => string 
                  = (cust, nameProcessor) => nameProcessor(cust);

I can now call GetNamePart passing the customer object and a function that does the processing I want. This code, using the JavaScript-like syntax, passes a function that extracts the first initial of the Customer's name:

var finitial: string
finitial = GetNamePart(cust1, function (c) 
{ 
  return c.name.substr(0, 1) 
});

I can do the same thing using the fat arrow syntax to specify the nameProcessor strategy function:

var finitial: string
finitial = GetNamePart(cust1, cust => cust.name.substr(0, 1) );

Creating Reliable Callbacks
In the same way that I can define that a function accepts a strategy function as a parameter, I can define a function that returns another function as its result. One warning: I'm first going to define what I'm sure looks like a foolish case, but it's just a stepping stone to defining a more realistic callback example.

I'll start by defining a function variable called GetFirstNameCreator that itself accepts no parameters, but returns another function (I'll call the function being returned the "callback function"). The callback function accepts a Customer object and returns a string. Listing 1 shows the code that both defines the variable and assigns a function to the variable to it using the JavaScript-like syntax. Inside GetFirstNameCreator function, I return the callback function which does the real work.

Listing 1: Using the JavaScript-Like Syntax To Define a Callback Function
var GetFirstNameCreator: () 
          => (cust: Customer) 
            => string 
            = function(): (cust:Customer) 
                          => string
                         {
                           return function (cust: Customer)
                           {
        		      return cust.name.substr(0, cust.name.indexOf(" "));
                           }
                         }

This code does the same thing with the fat arrow syntax:

var GetFirstNameCreator: () 
         => (cust: Customer) 
           => string 
             = () 
               => (cust) => cust.name.substr(0, cust.name.indexOf(" "));

As a side note, in that earlier column I pointed out that I think it's confusing that the fat arrow is used for what I regard as two purposes (specifying a return type and separating the parameter list from function body). In the previous code, for example, try to pick out which fat arrows are specifying return types (the first three) and which ones separate a parameter list from the body of the function (the last one). I suspect that a function that accepted a strategy function as a parameter and where that strategy function returned a callback function would take me hours to parse.

To use my function, I call GetFirstNameCreator to retrieve the callback function that does the real work and then call the callback function to get the customer's first name:

var fnc: (cust: Customer) => string;
fnc= GetFirstNameCreator();
var fname: string;
fname = fnc(cust1);

But let's take a more interesting case: let's say that my GetFirstNameCreator is a function within my Customer class, as shown in Listing 2. This is a more typical case of a method in a class returning a callback function that manipulates other members in the class. The code in Listing 2 defines GetFirstNameCreator as a function that returns another function (that function accepts no parameters and returns a string). Inside the GetFirstNameCreator function itself, I have a return statement that does return a function (written with the fat arrow syntax) compatible with that declaration -- that's the callback function. I've added line breaks and some additional parentheses to try and improve readability (feel free to think I've just added some clutter).

Listing 2: TypeScript Class with Callback Function
class Customer 
{
  name: string;
  GetFirstNameCreator(): () 
                          => string
                        {
                          return (
                                  () => this.name.substring(0,this.name.indexOf(" ")
                                );
                        }
}

The callback function doesn't need a parameter because, internally, it uses the this keyword to retrieve the name property of the Customer class it's part of. I used the fat arrow syntax in Listing 2 because that's the only reliable way to implement this callback function.

To use my Customer's callback function, I'd write something like this:

var fnc: () => string;
var fname: string;
fnc = cust1.GetFirstNameCreator();
fname = fnc();

If I had used the JavaScript-like syntax to declare my callback function things would go wrong when I used the callback function in my last line of code: The this keyword in my callback function would no longer be referring to the class of which it was a part. This is because, as all true JavaScript ninjas know, the this keyword is bound at the time that the function is called. On my last line of code, the this keyword wouldn't have been bound to my Customer object, but would instead be bound to … something else (probably the Window object).

But, because I used the fat arrow syntax, the this keyword is bound when the function is defined. As a result, this will still be referring to Customer class when the callback function is used in the last line of my code…which is probably what most developers would expect.

And this is the point of TypeScript, after all: code that, when it compiles, does what you think it should do.

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

  • AI for GitHub Collaboration? Maybe Not So Much

    No doubt GitHub Copilot has been a boon for developers, but AI might not be the best tool for collaboration, according to developers weighing in on a recent social media post from the GitHub team.

  • Visual Studio 2022 Getting VS Code 'Command Palette' Equivalent

    As any Visual Studio Code user knows, the editor's command palette is a powerful tool for getting things done quickly, without having to navigate through menus and dialogs. Now, we learn how an equivalent is coming for Microsoft's flagship Visual Studio IDE, invoked by the same familiar Ctrl+Shift+P keyboard shortcut.

  • .NET 9 Preview 3: 'I've Been Waiting 9 Years for This API!'

    Microsoft's third preview of .NET 9 sees a lot of minor tweaks and fixes with no earth-shaking new functionality, but little things can be important to individual developers.

  • Data Anomaly Detection Using a Neural Autoencoder with C#

    Dr. James McCaffrey of Microsoft Research tackles the process of examining a set of source data to find data items that are different in some way from the majority of the source items.

  • What's New for Python, Java in Visual Studio Code

    Microsoft announced March 2024 updates to its Python and Java extensions for Visual Studio Code, the open source-based, cross-platform code editor that has repeatedly been named the No. 1 tool in major development surveys.

Subscribe on YouTube