Click here to Skip to main content
13,346,487 members (65,376 online)
Click here to Skip to main content
Add your own
alternative version

Stats

8.3K views
65 downloads
23 bookmarked
Posted 29 Nov 2017

TypeScript for Object Oriented Developers in Easy Samples Part 1

, 2 Dec 2017
Rate this:
Please Sign up or sign in to vote.
TypeScript tutorial for object oriented programmers

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

  1. Setting up the environment.
  2. Creating a small HelloWorld typescript project.
  3. Variables and types in TypeScript.
  4. TypeScript's classes with properties.
  5. Extending classes in TypeScript.
  6. Implementing interfaces.
  7. Anonymouse classes.
  8. Generics in TypeScript.

 

Part 2 should cover the following topics (subject to change):

  1. Modules and Namespaces.
  2. LINQ like capabilities of TypeScript.
  3. JSON to TypeScript mapper.
  4. Promises and async programming.
  5. RxJS with TypeScript.
  6. 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:

  1. BasicTypeVariables.ts
  2. EnumVariables.ts
  3. ArrayVariables.ts
  4. TupleVariables.ts

 

Basic Types

Basic types are demo'ed within BasicTypeVariables.ts file. Here is its content:

// define number
let myNumber: number;

// define string
let myString: string;

// define any (can take any type
// - analogous to object in C#)
let myAny1: any = null;

// no type specified analogous to 'any'
let myAny2; 

// initialised to boolean
// even though the type is not specified
// you cannot change the type later
let myBool1 = true;

// type specified and initialize to false
let myBool2: boolean = false;

// compiler error
// cannot reassign the type to 'number'
myString = 5; 

myAny1 = 5; //OK

// OK
myAny2 = "hello world"; 

// any can be reassigned any type (like object in C#)
myAny1 = "Hi";

// casting:
// the two expressions below 
// are equivalent.
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, // can assign integer 
    Summer,
    Fall
}

let summerNumber: number;

// summerNumber = 2
summerNumber = Season.Summer; 

let summerName: string;

// summerName = "Summer"
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"];

// myStr="str1"
let myStr = arrayOfStrings[0]; 

// compiler error
// cannot assign an array of numbers
// to an array of string.
arrayOfStrings = [1, 2, 3];

// can contain any types like List<object> in C#
let arrayOfAny: any[] = [1, "str2", false];

// for any[] reassigning to an array 
// containing different types works fine
arrayOfAny = [true, 2, "str3"];

// another way to define array of strings
// by using Array<string>
let arrayOfStrings2: Array<string>;

// now arrayOfStrings2 contains ["str1", "str2", "str3", "str4"]
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]; // works fine

myTuple = [123, 123, 123]; // compiler error  

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
{
    //#region
    // read-write property FirstName
    private _firstName: string;
    get FirstName(): string
    {
        return this._firstName;
    }
    set FirstName(value: string)
    {
        this._firstName = value;
    }
    //#endregion

    //#region
    // read-write property LastName
    private _lastName: string;
    get LastName(): string
    {
        return this._lastName;
    }
    set LastName(value: string)
    {
        this._lastName = value;
    }
    //#endregion

    // read-only property FullName
    get FullName(): string 
    {
        let result: string = "";

        if (this.FirstName) 
        {
            result += this.FirstName;
            result += " ";
        }

        if (this.LastName)
        {
            result += this.LastName;
        }

        return result;
    }

    // constructor taking two optional arguments
    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:

//#region
// read-write property FirstName
private _firstName: string;
get FirstName(): string
{
    return this._firstName;
}
set FirstName(value: string)
{
    this._firstName = value;
}
//#endregion

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 taking two optional arguments
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;
    }

    // property overriding sample
    get LastName() 
    {
        return "SuperCaleFlageolisticExpialodocios";
    }

    constructor(employeeNumber:number, firstName:string, lastName:string)
    {
        // call constructor of the super class
        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:

// property overriding sample
get LastName() 
{
    return "SuperCaleFlageolisticExpialodocios";
}  

Now, take a look at the bottom of the Employee.ts file:

// function that takes Person object
// and displays it within alert dialog
function PrintPersonFullName(person: Person): void
{
    alert(person.FullName);
}

// create Employee object
let joeDoeEmployee: Person = new Employee(1, "Joe", "Doe");

// call PrintPersonFullName function on
// the joeDoeEmployee object
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)
{
    // call constructor of the super class
    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
{
    // '?' makes it optional property
    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
{
    //#region
    // read-write property FirstName
    private _firstName: string;
    get FirstName(): string
    {
        return this._firstName;
    }
    set FirstName(value: string)
    {
        this._firstName = value;
    }
    //#endregion

    //#region
    // read-write property LastName
    private _lastName: string;
    get LastName(): string
    {
        return this._lastName;
    }
    set LastName(value: string)
    {
        this._lastName = value;
    }
    //#endregion

    // constructor taking two optional arguments
    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:

// defining a method (function)
// using error notations
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.

Anonymouse Classes

I demonstrate Anonymouse 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:

// create anonymous class
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"); // call constructor  

We create an object of anonymouse 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();

    // add new item to the cache
    // throw exception if such id already exists
    AddItem(item: T)
    {
        if (this._mapObject.hasOwnProperty(item.UniqueId))
        {
            throw "item with the same id already exists in cache";
        }

        this._mapObject[item.UniqueId] = item;
    }

    // get item by id. 
    // if no item with such id exists, return null
    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:

// UniqueObject class implements IUniqueContainer 
// interface
class UniqueObject implements IUniqueIdContainer
{
    UniqueId: string;

    constructor(uniqueId: string)
    {
        this.UniqueId = uniqueId;
    }
}  

And then we create a concrete cache for such objects:

// define a cache storing UniqueObjects 
let uniqueObjectCache = new UniqueIdContainerItemsCache<UniqueObject>();  

Finally, we add an item to and get and item from the cache to test it:

// add an item to the cache
uniqueObjectCache.AddItem(new UniqueObject("1"));

// get an item from the cache by id
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

License

This article, along with any associated source code and files, is licensed under The Code Project Open License (CPOL)

Share

About the Author

Nick Polyak
Architect AWebPros
United States United States
No Biography provided

You may also be interested in...

Pro
Pro

Comments and Discussions

 
QuestionNyce Article (Y) thumbs up Pin
zubair_syed14-Dec-17 0:12
memberzubair_syed14-Dec-17 0:12 
AnswerRe: Nyce Article (Y) thumbs up Pin
Nick Polyak4-Dec-17 1:05
professionalNick Polyak4-Dec-17 1:05 
QuestionThanks! Pin
VitoCorleone7730-Nov-17 20:52
memberVitoCorleone7730-Nov-17 20:52 
AnswerRe: Thanks! Pin
Nick Polyak1-Dec-17 1:05
professionalNick Polyak1-Dec-17 1:05 
PraiseVery nice article Pin
per_edvin30-Nov-17 13:16
memberper_edvin30-Nov-17 13:16 
GeneralRe: Very nice article Pin
Nick Polyak30-Nov-17 14:26
professionalNick Polyak30-Nov-17 14:26 
QuestionNice work Pin
Sacha Barber30-Nov-17 2:40
mvpSacha Barber30-Nov-17 2:40 
AnswerRe: Nice work Pin
Nick Polyak30-Nov-17 4:16
professionalNick Polyak30-Nov-17 4:16 
GeneralRe: Nice work Pin
Sacha Barber30-Nov-17 5:22
mvpSacha Barber30-Nov-17 5:22 
GeneralRe: Nice work Pin
Nick Polyak30-Nov-17 6:00
professionalNick Polyak30-Nov-17 6:00 

General General    News News    Suggestion Suggestion    Question Question    Bug Bug    Answer Answer    Joke Joke    Praise Praise    Rant Rant    Admin Admin   

Use Ctrl+Left/Right to switch messages, Ctrl+Up/Down to switch threads, Ctrl+Shift+Left/Right to switch pages.

Permalink | Advertise | Privacy | Terms of Use | Mobile
Web03 | 2.8.180111.1 | Last Updated 3 Dec 2017
Article Copyright 2017 by Nick Polyak
Everything else Copyright © CodeProject, 1999-2018
Layout: fixed | fluid