Practical TypeScript

Copying Classes in TypeScript

If you need to create a version of a class from several sources or just want to merge default values with a user's input, object spreading solves your problem. JavaScript won't let you do this (yet) but TypeScript will.

One of the attractive features of TypeScript is how it incorporates features that aren't (yet) available in JavaScript. The object spread syntax that allows you to merge multiple objects into a single new class is a good example. Having access to the syntax is useful because it simplifies a common problem down to a single line of code.

First, some background: TypeScript uses a kind of duck typing that considers two objects to be "the same type" if they have matching properties. Here, as an example, is a CustomerClass (with two properties) and a CreditCustomer class (with two identical properties plus one more):

class Customer {
   firstName: string;
   lastName: string;}
class CreditCustomer {
   firstName: string;
   lastName: string;
   creditLimit: number;}

Thanks to the way TypeScript considers classes to be compatible, this code works:

let cust = new Customer();
let ccust = new CreditCustomer();
cust.firstName = "Peter";
cust.lastName = "Vogel";
cust = ccust;

TypeScript considers the classes to be compatible because CreditCustomer has all the properties that Customer has. As a result, as my code shows, I can assign a CreditCustomer object to a Customer variable.

Managing Dissimilar Classes
However, assigning a Customer object to a CreditCustomer variable does have at least one problem: There will be no explicit assignment of a value to the CreditCustomer's creditLimit variable because the Customer object has no corresponding property. This is where the object spread syntax that's part of the ECMAScript 6 proposal steps in and lets you assemble a complete set of values for a new object from a variety of sources.

For example, I can define a class called creditValue that has a single property called creditLimit:

class creditValue {
   creditLimit: number;
}

I can then instantiate it and set its creditLimit property:

let cv = new creditValue();
cv.creditLimit = 100;

Finally, using the object spread syntax, I can use my original Customer object and my new creditValue object as input to create a new CreditCustomer object with all of its properties set (the ellipsis in this syntax gathers up all properties not explicitly referenced):

let ccCust: CreditCustomer;
ccust = { ...cust, ...cv };

The new CreditCustomer object object in the ccust variable will have its firstName and lastName properties set from the cust variable and its creditLimit property set from the cv variable.

However, that's a lot of work just to set a single property. Fortunately, the object spread syntax also lets me use a property assignment expression as one of the inputs. That means I can also set the creditLimit value with code like this:

ccust = { ...cust, creditLimit: 100 };

Adding Flexibility
You're not limited to providing just two objects -- you can pull your values from as many inputs as you want. If a property name is repeated in multiple inputs, the later input's values win. This code, for example, uses three inputs, seting the cust variable's firstName to Jason in the last item:

ccust = { ...cust, ...scust, creditLimit: 100, firstName: "Jason" };

With this code, The firstName property on the ccust variable will be set to Jason regardless of what value the firstName property may have in scust. However, properties that have no values in later items will have no effect on properties in earlier items (I'll take advantage of this later to use object spread to combine user inputs with default values).

TypeScript, being type safe, will prevent you from assigning values to properties that don't exist on the target class (JavaScript does not). This code won't work, for example, because CreditCustomer doesn't have a property called totalSales:

ccust = { ...cust, scust, totalSales: 2100, firstName: "Jason" };

By the way (and if you care about the resulting JavaScript code), under the hood TypeScript is using its __assign helper to implement this functionality.

Exploiting Object Spread
That's all very interesting but you have to ask if it's useful.

One place you can use the object spread syntax to create a new copy of an object from an existing object. To create a new Customer from an existing Customer I can use code like this, providing just one item:

Let cust2: Customer;
cust2 = {...cust};

This gives me a whole new Customer object in cust2 which I can update without disturbing the values in the original cust object.

However, the object spread syntax just gives you a shallow copy of the Customer object. That will work fine for my simple Customer class but probably won't give you what you want with a class that holds anything other than a scalar value in its properties. For example, let's say my Customer object has a homeAddress property that holds an Address class:

class Customer {
  ... other properties ...
  homeAddress: Address;}

If I use object spread to copy this object, the Address object in homeAddress will be copied to the cust2 object. However, I won't get a new copy of the Address object -- only the reference to the Address object will be copied. As a result, any changes I make to the homeAddress property either through the cust or cust2 variables will show up in both places.

More usefully, I can use object spread to override values in some default object with a set of specified values. This code, for example, combines a defaultValues object with a userInput object to create a parm object. That parm object is then passed as a parameter to a method:

let defaultValues: getCustomerParms;
userInput.data = "???";
userInput.url = "www.phvis.com/Customers";
let userInput: getCustomerParms;
userInput.data = "A123";
parm = {...defaultValues, ...userInput}
getCustomer(parm)

Because the userInput object never has its url property set, parm will still have the original value from defaultValues in its url property. On the other hand, because userInput has its data property set, parm will have its data property set to A123.

Thanks to TypeScript you can start using the object spread syntax now while ensuring type safety: the best of both the JavaScript and the data-typed world in one bundle.

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
Upcoming Events

.NET Insight

Sign up for our newsletter.

I agree to this site's Privacy Policy.