TypeScript for the C# Developer: TypeScript Classes and Interfaces

Friday Jun 16th 2017 by Peter Shaw
Share:

Go further into the world of TypeScript and look at the concept of interfaces and classes.

If you read the previous article on this subject, "TypeScript for the C# Developer," you'll recall we covered the type system that TypeScript makes available, and how much alike to the C# type system it is.

In this article, we'll go a little bit further and look at the concept of interfaces and classes.

Classes in TypeScript are nothing more than a thin wrapper around already existing JavaScript class-orientated programming; they make things easier by abstracting away what a JavaScript class actually looks like, and making it look more like the type of class we, as software developers, are used to.

Interfaces, on the other hand, are a specific TypeScript construct that exist only in the TypeScript world and have no actual equal in JavaScript.

The Secret's in the Filename

Before we take a look at them both, however, there's a little bit of TypeScript magic that you need to know.

If you've looked at any TypeScript code, you'll note that the files usually have a ".ts" file extension.

When TypeScript comes across a file with this extension, it compiles it and produces a JavaScript file with the same name, but with a ".js" extension.

TypeScript also recognises the ".d.ts" extension too, but, upon encountering this, it does something a little bit different.

Instead of compiling it into a JavaScript file, it treats it as a set of in memory definitions, and uses them as references when compiling other parts of your code.

It's this trick that allows TypeScript to have definition files for popular JavaScript libraries such as JQuery, Angular, and Knockout, and use them to provide strong typing and Intellisense against the code you're writing.

In a way, these ".d.ts" files (normally known as TypeScript Definitions) are much like having external assemblies referenced in your C# program, but not actually needing the code in them until you come to run things.

You can find 1000s of these definition files at the 'TypeScript Definition library' (Definitely Typed).

This Web site now has so many different definition files available for all manner of JavaScript libs, that its listing is too big for GitHub to list everything available.

Your Own Definitions Can Benefit, Too

This, is turn, brings me back to the subject we started out discussing in the first place.

If you define an interface and place it in a ".d.ts" file, something like the following:

interface MyType {]
   id: number;]
   firstName: string;
   lastName: string;
   emailAddress: string;
   isMember: Boolean;
}

You'll then find that it acts entirely like the Intellisense system does for C# inside Visual Studio:

Showing an Intellisense-like tag
Figure 1: Showing an Intellisense-like tag

Not only that, but it also means that if you try to use a property you've not defined, or have defined as a different type, TypeScript's type system will kick in and prevent compilation of bad or incorrect code.

Because you defined it as a ".d.ts" file, it's all virtual, and the resulting JS file generated will not require the definition file once your code is deployed.

If all you need is to shape your data, this is by far the best way to achieve it. I find it works very well for defining the shape of JSON data that I send from a C# service to an HTML User interface, and, as long as I keep the two sides in sync with each other, things are usually very smooth.

There are a number of extensions in the Visual Studio gallery to help with this, including a couple that will actually generate ".d.ts" files for you at build time.

Three of the more worthy mentions available in the Visual Studio Store are:

I would encourage you to have a look through all the offerings, however, and decide for yourself which works best for you.

On with the Class…

As you've probably gathered by now, defining a class is very much the same. As in C#, interfaces and classes exist alongside each other, and, like C#, interfaces define, whereas classes implement.

Because of this, you can't define a class as a ".d.ts" file, but you can define an interface using either.

The key difference, as I mentioned before, is the physical production of a file.

For this reason, if all you need is to define the shape of something (and that includes a class with methods), your best defining it as an interface.

If you want to define and implement something, you define it as a regular-looking class.

For the most part, classes in TS look more or less the same as classes in C#:

export class Test {

   constructor() {

   }

   private myPrivateVar: number = 0;
   public myPublicVar: string = "Hello World";
   protected myProtectedVar: Boolean = true;

   private myFunction(parameter: string): number
   {
      return 0;
   }

   private myNonReturnFunction()
   {

   }
}

There are a few 'gotchas' to be aware of, though.

First and foremost, constructors are ALWAYS defined by using the "constructor" keyword, unlike C#, where the constructor is typically a public un-typed function with the same name as the defining class.

The "export" part is essential to make the class publicly accessible to other parts of the code. Although public and private modifiers are available, they apply ONLY to members inside the class, and not to the overall class itself. If you look back at our discussion on interfaces, you'll see the same applies there, also.

You don't have to put "export" on the class itself; you can put it as the last line in a file and assign to the variable "export," but it's generally preferred to have one class per file and export that class.

You also can use something called a "default export," which you use depending on a) how you want to structure your code and b) how you want to consume your classes.

If you use:

export class Test

You easily can define multiple classes in one file:

export class Test1 {
}
export class Test2 {
}
export class Test3 {
}

You then consume these classes in your using code, by using the curling braces version of the import statement:

import { Test1, Test2, Test3 } from 'myclasses';

Doing things this way, you can hold all your classes in one file, but only include what you need from that file, when you need it.

If you use "default:"

export default class Test1

You can ONLY have one class in the file; any others cannot be accessed by your calling code. To consume the code, you use:

import Test1 from 'myclasses';

If you study the ES6 specifications, you'll see that this is actually not a TypeScript thing, but it's the new ES6 way of defining classes for modern day JavaScript. TS just helps you by enforcing types and syntax in a compiler-based way.

TS Classes also support modifiers, but, unlike C#, they only support "private," "public," and "protected."

There is something very important to remember here, though.

Modifiers in TypeScript are used ONLY by TypeScript.

If you define a property as "private," for example, and then try to access that property from a regular JavaScript class against the class previously compiled using TS, you'll quickly find that it's just as fully accessible as anything that is/was defined public.

JavaScript itself as a language has no concept of property or function modifiers, so everything is public and always will be.

Having modifiers exists only to help you use TypeScript to write better code. It cannot enforce something that JavaScript by itself cannot do by default.

Just as with C#, "private" means accessible only inside that class, "public" means accessible by everything, and "protected" means accessible inside that namespace.

Namespaces are also implemented in TypeScript, but generally are not used all that much, unless you're writing framework and library code. You can define a namespace, exactly as you would define one in C#:

namespace MyNamespace {

}

And then, you can create your class definitions and interfaces inside of that.

ONE word of warning, though: If you do use interfaces, don't go overboard with them.

Unlike with C#, namespaces CANNOT be defaulted, so, for example:

namespace MyNamespace1 {
   namespace MyNamespace2 {
      namespace MyNamespace3 {
         export class MyData{}
      }
   }
}

Must be quoted in full when using it EG:

private myData:MyNamespace1.MyNamespace2
   .MyNamespace3.MyData = new MyNamespace1
   .MyNamespace2.MyNamespace3.MyData();

You can't shortcut any of this, and there's nothing in TypeScript that works in the same way as a using does in C#. Long nested namespaces mean LOTS of typing.

The rest is quite straightforward, as I mentioned in the article on Types. A type name is a suffix, suffixed using a ": ", classes use them in the same way for parameters, properties and function returns. The TS compiler also will enforce return types.

If you define a function to return something, don't return something from that function, or if you miss a return path because of a missing IF clause, for example, you'll get build errors just as you would with C#.

That's all for this installment. Join me for the third and final part, where we'll take a look at generics, and I'll show you a few tricks that will allow you to add things like C#-style enumerable lists to your TS code.

Shawty

Share:
Home
Mobile Site | Full Site
Copyright 2017 © QuinStreet Inc. All Rights Reserved