Introduction
As Microsoft turned towards JavaScript for web programming, they, under great Anders Hejlsberg, decided to create an object oriented language that would compile to Java Script to run in all web browsers, but would bring some order to the chaotic world of weak typed, non-OOP web programming.
Syntatically, TypeScript is very similar to JavaScript. In fact, JavaScript programs would run within TypeScript without any change. However, TypeScript allows to bring the Web programming into the realm of the Object Oriented Development. So, I view TypeScript as an adaptor - it adapts the JavaScript to object oriented concepts.
Another great achievement of TypeScript is that it provides the latest and greatest features available in the latest ECMAScript 6 (not supported yet by many browsers) and transcribes them to an earlier JavaScript version e.g. ECMAScript 5. So developing in TypeScript automatically makes your code multibrowser and multiplatform. Without TypeScript the developers would have to employ some other tool to achieve a similar result, e.g. BabelJS.
Most of TypeScript tutorials you find on the Web are for the JavaScript developers who want to study TypeScript and need to be introduced to the object oriented concepts.
Here I adopt a different approach - explaining TypeScript to the object oriented developers. My purpose here is to provide very simple snippets of code that would highlight specific features so that an experienced Java or C# developer would be able to understand the feature of the language almost without reading the text.
Required Background
I assume some basic familiarity with OOP concepts and one of the OOP languages - preferrably C# or Java. I also expect some very rudimentary understanding of HTML programming and JavaScript. For those who need a refresher on HTML and JavaScript - here is an article I wrote several years ago: HTML5, JavaScript, Knockout, JQuery, Guide for Recovering Silverlight/WPF/C# Addicts. Part 1 - JavaScript and DOM.
Topics to be Covered in Parts 1 and 2
Part 1 covers the following topics
- Setting up the environment.
- Creating a small HelloWorld typescript project.
- Variables and types in TypeScript.
- TypeScript's classes with properties.
- Extending classes in TypeScript.
- Implementing interfaces.
- Anonymous classes.
- Generics in TypeScript.
Part 2 should cover the following topics (subject to change):
- Modules and Namespaces.
- LINQ like capabilities of TypeScript.
- JSON to TypeScript mapper.
- Promises and async programming.
- RxJS with TypeScript.
- JSX
Development Tools
I tried several development tools. For typescript development I liked Visuals Studio Code. But all of those tools (including Visual Studio Code) require a lot of extra software installed and configured. Explaining all of this would polute this article with unnecessary details. So, for this article I decided to stick with Visual Studio 2017. Perhaps later I'll write another article on various typescript environments.
As mentioned above, I use VS2017 as my development tool and run most of the samples within Chrome browser (even though it probably does not matter much, since all TypeScript features should be browser independent).
Setting up the VS2017
Even though, VS2017 contains most of TypeScript features out of the box, I installed the latest version of typescript for VS2017 (in my case it was version 2.6). To download it, you can simply follow the links from Typescript extensions links.
Code Location
Code for this article is located on Github at the following url: Typescrit for OOP Developers Part 1 Code.
Type Script HelloWorld Sample
We will start with the smallest and simplest possible Typescript sample, which, however, highlights some of TypeScript features and, most importantly, teached how to create and set the VS2017 project that uses TypeScript.
Creating the Project, Setting the Properties
I recommend creating your TypeScript projects as Empty ASP.NET Web Application project. To do it, click New->Project option from the file menu. In the open dialog click on Visual C#->Web option on the left hand side and choose "ASP.Net Web Application (.NET Framework)" option in the main panel. Do not forget to click "Create directory for solution" checkbox; otherwise the created solution file will reside in the main folder (which is probably not what you want):
Now let us create our main HTML file called "Default.html". Start right clicking on the project within the solution explorer and choosing Add->New Item option. In the open dialog choose Visual C#->Web on the left and "HTML Page" in the main panel. Choose name "Default.html" for the item and click button "Add":
You should make the newly created Default.html file - the start up page. In order to do it, right click on the file within the solution explorer and choose "Set As Start Page" option:
Now, create a project folder for all Typescript and JavaScript files by right clicking on the project and choosing Add->New Folder. Call the new folder "Scripts".
Create a TypeScript file HelloWorld.ts within "Scripts" folder by right clicking on it and choosing Add->New Item. Within the opened dialog use the search text at the top right corner to search for TypeScript templates. Choose TypeScript File option in the middle section, call the file HelloWorld.ts (at the bottom) and click Add button:
Now place the following code into HelloWorld.ts file:
class HelloWorldPrinter
{
static Print(): void
{
alert("Hello World!");
}
}
Within the body tag of the Default.html file, add the following script tags:
<body>
<script src="Scripts/HelloWorld.js"></script>
<script>
HelloWorldPrinter.Print();
</script>
</body>
You can actually add a reference to a TypeScript file by dragging it into the html code, so, instead of writing <script src="Scripts/HelloWorld.js"></script>
, you can simply drag the HelloWorld.ts file over to the place within <body> tag.
Now try running the application - press Debug button or use DEBUG->Start Debugging menu item. Your default browser should start (in my case it is Chrome) and show the "Hello World!" text within the alert window:
Note that you can put a breakpoint within HelloWorld.ts file and it is going to be hit. Several years ago, Visual Studio debugger would only work with the MS Internet Explorer browser, now, apparently it works with other browsers too, at least with Chrome.
The other way to start the application would be to right click on the Default.html file and choose "View in Browser" menu option. In that case, however you won't be able to debug TypeScript (you will still be able to use the JavaScript debugger within your browser, but it will work only on the generated JavaScript and not on the original TypeScript).
Now take a look at the project's properties. In particular, we are interested in the "TypeScript Build": tab:
<img src="1217899/ProjProps.png" class="" alt="" />
Note that "Compile on Save" option ensures that the there is an attempt to generate JavaScript files every time the developer saves TypeScript file.
Clicked "Generate source maps" generates files to map the JavaScript files back to TypeScript in order to allow TypeScript debugging.
Another interesting property is "ECMAScript Version". This property specifies the version of the JavaScript into which TypeScript compiles. "ECMAScript 5" is a very reasonable choice since most of the browsers support it, while the newer options e.g. "ECMAScript 6" is not so widely supported.
This three properties are important to understand but VS2017 sets them for you to correct values by default.
The Code
Let us take another look at the HelloWorld.ts code:
class HelloWorldPrinter
{
static Print(): void
{
alert("Hello World!");
}
}
Unlike in C# you do not need the "public" keyword in front of the class or in front of the methods - the classes and methods are public by default.
Like in C# or Java, TypeScript has classes and the methods can either be defined static (with the "static" keyword) or instance (without any keyword).
Public static methods can be accessed outside of the class via the class name as we do it in our Default.html file:
HelloWorldPrinter.Print();
Several Important Notes
In the HelloWorld sample I on purpose went over the steps of creating the project, creating the files, changing the project properties. In the rest of the article I'll be concentrating on the code instead.
Most of the basic programming language constructs in TypeScript are similar to JavaScript which in turn borrowed them from Java, so if
, while
and for
and other constracts are very similar to those in C++, Java and C#.
In the rest of the samples I'll concentrate on TypeScript specific features and best practices, omitting those that are similar to JavaScript or the OO languages listed above. E.g. I am not describing while
loop because it is exactly the same as in the languages above.
Variables
The code for the Variables sample is located under Variables/Variables.sln solution. Note that the code does not compile - I introduced some compilation errors on purpose to show what will result in an error in TypeScript.
TypeScript files that we are interested in are under project folder Scripts:
- BasicTypeVariables.ts
- EnumVariables.ts
- ArrayVariables.ts
- TupleVariables.ts
Basic Types
Basic types are demo'ed within BasicTypeVariables.ts file. Here is its content:
let myNumber: number;
let myString: string;
let myAny1: any = null;
let myAny2;
let myBool1 = true;
let myBool2: boolean = false;
myString = 5;
myAny1 = 5;
myAny2 = "hello world";
myAny1 = "Hi";
myAny2 as string;
(<string>myAny2);
The code comments should give you a sense of what it going on.
Note that in order to define a variable, we need to use the keyword "let" (unlike in JavaScript where "var" is employed). We can also use "var" here but then our scopes will be screwed - the way they are in regular JavaScript. The keyword "let", however, limits the scope of the defined variable to the curly bracketted block - the way it is done in the major OO languages and the way it should be.
The type of the variable can be specified following the colon after the variable name. There are several basic types including number, string and boolean. There is also a special type any. Variables of this type can assume any other type. Type any is analogous to type object in Java or C#. If a variable is not assigned a type and not initialized, it is assumed to be of type any.
If a variable is not assigned a type but is initialized to some number, string or boolean within the declaration line, TypeScript assumes that it is defined to be of the corresponding type.
Once a TypeScript variable is assumed to be of some (non any) type, it cannot be assined a value of any other type - the compiler will report an error.
Enumerations
One can define enumerations in TypeScript in a way very similar to C#. Here are the contents of EnumVariables.ts file
enum Season
{
Winter,
Spring = 1,
Summer,
Fall
}
let summerNumber: number;
summerNumber = Season.Summer;
let summerName: string;
summerName = Season[Season.Summer];
The only tricky part here is retrieving the name of the enum. One gets it via array index notations: summerName = Season[Season.Summer];
.
Arrays
ArrayVariables.ts file shows the ways to define and initialize an array (or list or vector):
let arrayOfStrings = ["str1", "str2", "str3"];
let myStr = arrayOfStrings[0];
arrayOfStrings = [1, 2, 3];
let arrayOfAny: any[] = [1, "str2", false];
arrayOfAny = [true, 2, "str3"];
let arrayOfStrings2: Array<string>;
arrayOfStrings2 = [...arrayOfStrings, "str4"];
let i = 5;
The part of the code above that is not self explanotary is the one that contains the so called spread operator "...":
arrayOfStrings2 = [...arrayOfStrings, "str4"];
The spread operator syntatically unwrapps the array, turning it into its comma separated members. It is used here to do array concatination, it can also be used to create methods with variable number if input arguments - analogous to the params keyword in C#.
Tuples
Here is the content of the TupleVariables.ts file:
let myTuple: [number, string, boolean];
myTuple = [123, "123", true];
myTuple = [123, 123, 123];
Nothing unusual here.
Classes and Interfaces
Example of a class with Properties
Solution ClassAndProperties.sln contains an example of Person
with three properties:
class Person
{
private _firstName: string;
get FirstName(): string
{
return this._firstName;
}
set FirstName(value: string)
{
this._firstName = value;
}
private _lastName: string;
get LastName(): string
{
return this._lastName;
}
set LastName(value: string)
{
this._lastName = value;
}
get FullName(): string
{
let result: string = "";
if (this.FirstName)
{
result += this.FirstName;
result += " ";
}
if (this.LastName)
{
result += this.LastName;
}
return result;
}
constructor(firstName?: string, lastName?: string)
{
this.FirstName = firstName;
this.LastName = lastName;
}
}
Properies FirstName
and LastName
are read-write propertis, while the property FullName
is read-only.
Let us take a closer look a one of the properties:
private _firstName: string;
get FirstName(): string
{
return this._firstName;
}
set FirstName(value: string)
{
this._firstName = value;
}
Property FirstName
has private field _firstName
as its backing store. The getter and setters are defined as two separate methods within the class scope (not the way it is done in C#). Unfortunately (also unlike in C#) you can have different privacy settings for the getter and the setter of the same property: e.g. setting FirstName
setter private or protected will result in a compiler error.
Another interesting part of the class is the constructor:
constructor(firstName?: string, lastName?: string)
{
this.FirstName = firstName;
this.LastName = lastName;
}
Question mark after a variable name means that the argument is optional, e.i. if it is not passed, it is assumed to be in undefined state.
TypeScript does not allow method or constructor overloading, but it allows using optional arguments or arguments with default values. Of course such arguments should always be at the end of the argument list. Such functionality allows pretty much to match the method and constructor overloading of the OO languages.
Just in order not to return to it later, here is an example of a similar constructor with default value args instead of the optional:
constructor(firstName: string = "Unknown", lastName: string = "Unknown")
{
this.FirstName = firstName;
this.LastName = lastName;
}
Also, note that the methods, properties and fields are public by default, but if you want to limit their visibility, you need to add "protected" or "private" keyword in front of the variable name.
Now look at the bottom of Person.ts file:
Another important thing to note - the classes methods, props and fields can only be accessed via the keyword "this" within the class. If you try to remove "this." in front of any of them, the compiler will report an error.
let people: Array<Person>;
people = [new Person("Joe", "Doe"), new Person("Jane", "Dane")];
let result: string = "";
for (let person of people)
{
result += person.FullName + "\n";
}
alert(result);
I create global scope variable people
as Array<Person>;
. Then I assing it an array of two Person
objects. Now, take a look at the iteration over the array items:
for (let person of people)
{
...
}
This is equivalent to C# foreach(var person in people)
iterator.
If you run the sample, it will display the full names of the two people within an alert dialog:
Extending a Class
Solution ExtendingClass.sln shows how to extend a class in TypeScript.
Class Person
of this solution is exactly the same as in the previos class, but we also introduce class Employee
which extends class Person
:
class Employee extends Person
{
private _employeeNumber: number;
get EmployeeNumber()
{
return this._employeeNumber;
}
get LastName()
{
return "SuperCaleFlageolisticExpialodocios";
}
constructor(employeeNumber:number, firstName:string, lastName:string)
{
super(firstName, firstName);
this._employeeNumber = employeeNumber;
}
}
It introduces a read-only property EmployeeNumber
backed up by private _employeeNumber
field.
Also, note, that all methods and properties in TypeScript are virtual and defining a property or method of the same name in a sub-class - overrides them automatically (no need to use "override" keyword). To prove it, I overrode LastName<code>'s getter to always return "SuperCaleFlageolistExpialodocios" for the <code>Employee
objects:
get LastName()
{
return "SuperCaleFlageolisticExpialodocios";
}
Now, take a look at the bottom of the Employee.ts file:
function PrintPersonFullName(person: Person): void
{
alert(person.FullName);
}
let joeDoeEmployee: Person = new Employee(1, "Joe", "Doe");
PrintPersonFullName(joeDoeEmployee);
We define a funtion PrintPersonFullName
that takes a Person
object and we pass an Employee object to it instead. The function simply displays the FullName
property of the object within the alert dialog. The full name we are going to see is "Joe SuperCaleFlageolisticExpialodocios" and not "Joe Doe" which means that the LastName
property is virtual and was overridden.
Another interesting thing to note is calling super(...)
method within the constructor. It calls the constructor of the super class:
constructor(employeeNumber:number, firstName:string, lastName:string)
{
super(firstName, firstName);
this._employeeNumber = employeeNumber;
}
This is analogous to super
in Java or base
in C#.
Finally, take a look at our Default.html, the place where the scripts are defined:
<body>
<script src="Scripts/Person.js"></script>
<script src="Scripts/Employee.js"></script>
</body>
Note that we have to import the scripts in the same order in which they are being used. Importing Employee.js before Person.js would result in an error. We'll discuss it in more detail later.
Implementing Interfaces
Take a look at ImplementingInterface project, under Scrips folder. It demonstrates how to implement multiple interfaces.
IFirstNameContainer
interface contains FirstName
property:
interface IFirstNameContainer
{
FirstName: string;
}
ILastNameContainer
interface contains LastName
property:
interface ILastNameContainer
{
LastName: string;
}
IMiddleNameContainer
contains MiddleName
property:
interface IMiddleNameContainer
{
MiddleName?: string;
}
Note that the '?' question mark makes the property options, i.e. the interface won't 'insist' on the class having it.
Now, IPerson
interface, implements all three of the interfaces listed above:
interface IPerson
extends
IFirstNameContainer,
ILastNameContainer,
IMiddleNameContainer
{
}
Our Person
class implements IPerson
interface:
class Person implements IPerson
{
private _firstName: string;
get FirstName(): string
{
return this._firstName;
}
set FirstName(value: string)
{
this._firstName = value;
}
private _lastName: string;
get LastName(): string
{
return this._lastName;
}
set LastName(value: string)
{
this._lastName = value;
}
constructor(firstName?: string, lastName?: string)
{
this.FirstName = firstName;
this.LastName = lastName;
}
}
Note Person
does not implement MiddleName
property and still typescript does not show any problem since the property is optional.
At the bottom of the Person.ts file I define a function PrintFullName
using arrpw (lambda) notations instead of the usual JavaScript way:
let PrintFullName = (person: IPerson) =>
{
let result: string = "";
if (person.FirstName)
{
result += person.FirstName + " ";
}
if (person.MiddleName)
{
result += person.MiddleName + " ";
}
if (person.LastName)
{
result += person.LastName;
}
alert(result);
}
Finally I call this function to display the full name in the alert dialog:
PrintFullName(new Person("Joe", "Doe"));
Note that I could have defined the PrintFullName
function to take an interesting combination of interface;
let PrintFullName = (person: IFirstNameContainer & ILastNameContainer & IMiddleNameContainer)...
This is a nice feature - not available in C#. In general we do not have to define the joint interface, we can require a method argument to satisfy multiple interface at the same time.
Another interesting thing is that our Person
class does not actually have to implement the interface in order for our method PrintFullName
still to work (even though the method is built to work with the interface, not against the class). You can comment out the the implements IPerson
part and verify that everything still works. This is because the compiler will check that all the properties required by the interface are still there and will allow the compilation to proceed.
Anonymous Classes
I demonstrate Anonymous Classes capability (which, BTW, is not present in C#, but is part of Java) in AnonymouseClasses.sln solution.
IPerson
interface in this project is very simple:
interface IPerson
{
FirstName: string;
LastName: string;
}
Now look at the top of Person.ts file:
let person = new class implements IPerson
{
private _firstName: string;
get FirstName(): string
{
return this._firstName;
}
set FirstName(value: string)
{
this._firstName = value;
}
private _lastName: string;
get LastName(): string
{
return this._lastName;
}
set LastName(value: string)
{
this._lastName = value;
}
constructor(firstName: string, lastName: string)
{
this.FirstName = firstName;
this.LastName = lastName;
}
}("Joe", "Doe");
We create an object of anonymous class that implements IPerson
interface providing the implementation at the same time as we create an object.
Generics
GenericsSample.sln provides a very simple sample that uses generics.
In general, we create a very primitive cache for object that implement IUniqueIdInterface
:
interface IUniqueIdContainer
{
readonly UniqueId: string;
}
Here is the code for the cache:
class UniqueIdContainerItemsCache<T extends IUniqueIdContainer>
{
private _mapObject = new Object();
AddItem(item: T)
{
if (this._mapObject.hasOwnProperty(item.UniqueId))
{
throw "item with the same id already exists in cache";
}
this._mapObject[item.UniqueId] = item;
}
GetItem(uniqueId: string) : T
{
if (this._mapObject.hasOwnProperty(uniqueId))
{
return this._mapObject[uniqueId] as T;
}
return null;
}
}
We define a concrete realization of the IUniqueIdContainer
interface:
class UniqueObject implements IUniqueIdContainer
{
UniqueId: string;
constructor(uniqueId: string)
{
this.UniqueId = uniqueId;
}
}
And then we create a concrete cache for such objects:
let uniqueObjectCache = new UniqueIdContainerItemsCache<UniqueObject>();
Finally, we add an item to and get and item from the cache to test it:
uniqueObjectCache.AddItem(new UniqueObject("1"));
let objFromCache = uniqueObjectCache.GetItem("1");
Summary
The purpose of this article is to provide a TypeScript tutorial for the developers that come with object oriented background.
Here I discuss the core OO features of the language. In the next installment I hope to discuss some deeper and more complex paradigms including mappings from JSON and asynchronous programming.
History
Did some work on improving grammar and spelling Dec/3/2017