Hands-On TypeScript for C# and .NET Core Developers

3.7 (3 reviews total)
By Francesco Abbruzzese
  • Instant online access to over 8,000+ books and videos
  • Constantly updated with 100+ new titles each month
  • Breadth and depth in over 1,000+ technologies
  1. Introduction to TypeScript

About this book

Writing clean, object-oriented code in JavaScript gets trickier and complex as the size of the project grows. This is where Typescript comes into the picture; it lets you write pure object-oriented code with ease, giving it the upper hand over JavaScript. This book introduces you to basic TypeScript concepts by gradually modifying standard JavaScript code, which makes learning TypeScript easy for C# ASP.NET developers.

As you progress through the chapters, you'll cover object programming concepts, such as classes, interfaces, and generics, and understand how they are related to, and similar in, both ES6 and C#. You will also learn how to use bundlers like WebPack to package your code and other resources. The book explains all concepts using practical examples of ASP.NET Core projects, and reusable TypeScript libraries. Finally, you'll explore the features that TypeScript inherits from either ES6 or C#, or both of them, such as Symbols, Iterables, Promises, and Decorators.

By the end of the book, you'll be able to apply all TypeScript concepts to understand the Angular framework better, and you'll have become comfortable with the way in which modules, components, and services are defined and used in Angular. You'll also have gained a good understanding of all the features included in the Angular/ASP.NET Core Visual Studio project template.

Publication date:
October 2018
Publisher
Packt
Pages
460
ISBN
9781789130287

 

Chapter 1. Introduction to TypeScript

The implementation of large JavaScript code bases in modern, rich client web applications has always pushed more in the direction of preventing hard-to- find bugs, with exhaustive checking at compile time. TypeScript meets this requirement by transforming JavaScript into a strongly typed language; that is, into a language that requires declarations and type specifications for all variables and properties. In fact, strong typing allows compile-time type checking that prevents the misuse of variables and functions while variable declarations avoid the variable names misspelling that often causes these bugs in JavaScript.

This chapter explains the TypeScript manifest, how to install it and add it to your ASP.NET core projects, how to organize files and compile them to JavaScript, and the basics of TypeScript configuration. Then the chapter introduces the basics of types and variable declarations. 

The following topics will be covered in this chapter:

  • Installation on Windows and the TypeScript mission
  • Adding TypeScript to ASP.NET core web projects and debugging it
  • Basics of TypeScript configuration
  • Simple types, enums, and basics of Union types
  • Variable declarations, scoping, expressions, casting, and string interpolation
 

Basics and installation


This section discusses the main motivations behind TypeScript and how to install the TypeScript SDK. Installation concerns just developers' machines since TypeScript code is completely compiled into JavaScript code and doesn't need any runtime library to be executed.

 

Adding types to JavaScript

TypeScript is a language that is transpiled to JavaScript; that is, TypeScript code is processed to generate JavaScript code.

As a first approximation, you may imagine that TypeScript code is obtained by adding type declarations of variables to usual JavaScript code. For instance, let's consider this JavaScript code:

var firstName = "Francesco";
var surName = "Abbruzzese";

function fullName(x, y, spaces){
    x + Array(spaces+1).join(' ') + y;
}
>fullName(firstName, surName, 3)
>"Francesco   Abbruzzese"

TypeScript adds a type declaration to the firstName and surName variable declarations, and it also adds types to the fullName function arguments, and to the function's return value:

var firstName: string = "Francesco";
var surName: string = "Abbruzzese";

function fullName(x: string, y: string, spaces: number): string{
    x + Array(spaces+1).join(' ') + y;
}

TypeScript and JavaScript code are very similar, the only difference being the colon followed by the type immediately after each variable or argument declaration and each function declaration.

In this very simple example, the JavaScript code generated by TypeScript transpilation is identical to the code written directly in JavaScript, so what is the advantage of using TypeScript?

Simple: type checking! The TypeScript compiler verifies type compatibility, thus immediately discovering errors that might otherwise manifest themselves with strange behaviors at runtime.

Suppose, for instance, that we call fullName with its arguments in the wrong order:

fullName(3, firstName, surName)

 

 

 

 

The TypeScript transpiler immediately discovers the error since 3 is not a string and surName is not a number, while JavaScript tries to automatically convert types and gets the wrong result:

>fullName(3, firstName, surName)
>"3Francesco"

Note

TypeScript types are used just to perform compile-time checks, and do not influence JavaScript code generated by the transpiler. In a few words, types disappear completely in the transpiled code. This is by design, because TypeScript was conceived to maintain the same JavaScript semantics and principles while helping with compile-time checks.

Using JavaScript of the future now!

While TypeScript was initially conceived to perform better compile-time checks than JavaScript, very soon its mission was extended to mitigate the different support for new JavaScript standards which are at the moment, ECMAScript 6-8. TypeScript includes most of ECMAScript 6-8's important features, but you may target the transpiled code at previous JavaScript versions. When a feature is not available in the target JavaScript version, the TypeScript transpiler creates code that simulates this feature in the target JavaScript version.

For instance, TypeScript includes ECMAScript 6 classes that will be covered in Chapter 4, Using Classes and Interfaces, and ECMAScript 8 async/await, which will be covered in the Promises and async/await notation section of Chapter 9, Decorators and Advanced ES6 Features. Here is an example of async/await use:

async functionasyncAwaitExample(url: string): string{
    let response= await fetch(url);     
    return await response.text(); 
}

The syntax is completely analogous to C# async/await.

Note

Since TypeScript was conceived to be JavaScript + compile-time type checks, all its current and future versions will include only runtime features that are part of some ECMAScript standard.

 

Installing the Visual Studio 2017 TypeScript SDK

As a default, Visual Studio 2017 installers automatically install TypeScript SDK, and as long as you keep Visual Studio updated, you should always have a recent version of the TypeScript SDK. Anyway, if for some reason TypeScript was not installed, or if you want to be sure you have the latest version of the TypeScript SDK, proceed as follows.

Go to Program and Functionalities in your computer's Control Panel and verify that TypeScript SDK is installed, and which version is installed:

In this case, TypeScript is already installed and its version is 2.8.3.0.

Open Visual Studio 2017 and go to Tools | Extensions and Updates:

When the Extensions and Updates window opens, in the left-hand menu, select Online | Visual Studio Gallery | Tools, and then type TypeScript in the text box in the upper-right corner.

A few seconds after you finish typing TypeScript, you should see all available versions of the TypeScript SDK. Select the most recent version:

In this case, the most recent version is 2.8.3.

If the version you found is more recent than the one already installed, click on the list item to select it, and then click on the Download button that appears to download the TypeScript SDK installer. When the download completes, double-click on the installer to install the SDK.

Installation of Node.js-based TypeScript compiler

In the Using VS Code section of Chapter 8, Building Typescript Libraries, we will learn TypeScript development without Visual Studio. In this case, we need the Node.js-based TypeScript compiler that is independent of Visual Studio.

As a first step, go to the Node.js website at https://nodejs.org/en/ and download the recommended version of Node.js.

Once installation is complete, open a Windows Command Prompt and type node -v to verify Node.js has been installed properly, and to verify its version.

We also need npm, the Node.js packages handler (a kind of NuGet for Node.js packages). npm is automatically installed with Node.js. Type npm -v in the Windows Command Prompt to verify its proper installation and its version.

Here is the result of typing these commands in a Windows Command Prompt:

Now, installing TypeScript compiler is quite easy; just type npm install -g typescript. This will install the last version of the TypeScript compiler globally on your computer.

If you need a different version of the TypeScript compiler for a specific project, you may install it locally for that project folder, while all other projects will continue using the version installed globally. This can be done as follows:

  1. Open Command Prompt in the project folder.
  2. Suppose the version you would like to install is 2.7.1, then type the following command: 
npm install --save-dev [email protected]
 

Adding TypeScript to your web projects


Once you have the TypeScript SDK installed, adding a TypeScript code file to an ASP.NET Core project is straightforward. Your TypeScript file will be compiled in a JavaScript file that you may call, as usual, in any View. However, you may debug your code and put breakpoints directly in the TypeScript source.

 

Your first TypeScript file

Let's open Visual Studio and create an ASP.NET Core project 2.x named TypeScriptTests:

 

Click OK, and in the new window that appears, select an MVC 2.x application with No Authentication (we need just a View for where to call our code):

Once the project finishes loading, run it to verify that the project has been scaffolded properly. Then, in the solution explorer, right-click on the wwwroot node and select Add | New Folder to add a new folder named ts:

 

Finally, right-click on the ts folder and select Add New Item. The following window opens:

In the left-hand menu, select ASP.NET Core | Web | Scripts. Then select the TypeScript File and name it tests.ts.

Let's add some test code to the newly added test.ts file:

var firstName: string = "Francesco";
var surName: string = "Abbruzzese";

function fullName(x: string, y: string, spaces: number): string {
    return x + Array(spaces+1).join(' ') + y;
}

alert(fullName(firstName, surName, 3)+" Hello");

Thanks to declarations and strong typing, the Visual Studio editor helps us with IntelliSense: 

As soon as we save the file, Visual Studio invokes the TypeScript compiler to transpile our file into a test.js JavaScript file:

In the case of errors, the JavaScript file is not generated and all errors are displayed in the editor as soon as we save the file. Let's try this behavior by misspelling the join method:

When we build the project, all TypeScript errors are also added to the Error List panel, as follows:

 

Running and debugging TypeScript code

Running and debugging TypeScript code is straightforward. It is enough to add the transpiled JavaScript file to a view. Let's add our previous test.js file to the bottom of the Home/Index.cshtml view:

@section Scripts{ 
    <script src="~/ts/tests.js"></script>
}

It is worth remembering that Script is a section defined in the Layout view of the ASP.NET Core MVC template. It allows all views to place the JavaScript code they need in the right place in the Layout view. When you run the project, an alert box should appear as soon as the website's default page is loaded in the browser, as shown in the following screenshot:

Thanks to the test.js.map map file generated by the TypeScript compiler, it is possible to debug the TypeScript source instead of the JavaScript transpiled code. In fact, map files contain all the information needed to map each position in the transpiled file into a position in the source file. Map files are not a peculiarity of TypeScript but a well-recognized standard, since they are also used for mapping minimized JavaScript files to their sources.

 

Let's place a breakpoint on the last instruction of test.ts and run the project:

Once the breakpoint is hit, you may benefit from all the Visual Studio debugging features you are used to when debugging C# code.

You may see values by hovering over variables with the mouse:

Or, you can use a Watch 1 window:

You also have access to the calls stack, to an immediate window, to the intellitrace, and to all other C# code features you are used to.

 

TypeScript compiler options


As a default, Visual Studio compiles all TypeScript files in your project into JavaScript files with the same names, but with the .js extension, and they are placed in the same folder. Moreover, for each .js file, it creates a file with the same name but with a .js.map extension that is called a map file. Map files map locations in the generated JavaScript files to locations in their TypeSctipt sources, thus enabling debugging on the TypeScript sources. This default behavior of the TypeScript compiler may be changed in two ways: either by forcing Visual Studio to invoke the TypeScript compiler with different parameters, or by specifying the desired options in a configuration file.

Specifying parameters for the TypeScript compiler

All parameters Visual Studio uses to invoke the TypeScript compiler may be edited in the project properties by right-clicking on the project icon and selecting Properties:

In the window that appears, select TypeScript Build. Here, you may change several options:

  • The TypeScript version from the ones installed.
  • The ECMAScript version of the JavaScript code generated. At the moment, ECMAScript 5 is supported by all mainstream browsers, while higher versions have incomplete support, so it is the advised choice for most applications.
  • Whether to compile on save or not. If this option is deselected, TypeScript files are compiled only when the whole project is built. This possibility may be useful in complex projects in which the TypeScript build is quite slow because after transpilation, large files are first minimized and then bundled into chunks. 
  • To keep, or not, all TypeScript source comments in the generated JavaScript files.
  • To choose a different directory for both the JavaScript and map files created by the compiler.
  • To emit JavaScript code, or not, in the case of errors.
  • To emit map files or not. If you want to debug TypeScript sources, you should always emit map files.
  • The directory in which to look for TypeScript files to compile. If this directory is not provided, the project root is taken.

Let's unselect the Compile on save option, save the changes, modify something in the test.ts file (for instance, change the Hello string to Hello world), and verify that the file is not compiled on save. Have a look at the last modification date of the test.js file. After this test, do not forget to select the Compile on save option again.

Now, let's try to put all the files generated by the TypeScript compiler in the js folder: 

After that, if you change something in the test.ts file and save it, test.js and test.js.map will appear in the .js folder:

TypeScript configuration file

A TypeScript configuration file allows us to specify several more options. We may specify several input directories instead of just one, we may specify paths/files to be excluded from compilation, or we may specify several more compilations flags. 

If you want Visual Studio to invoke the TypeScript compiler automatically, you must give the project TypeScript configuration file its default name, which, tsconfig.json. Otherwise, you must invoke the TypeScript compiler manually and pass it the configuration file name as a command-line parameter.

In order to add a TypeScript configuration file to your project, right-click on the project icon in the project explorer and select Add New Item:

Then select TypeScript JSON Configuration File. The configuration file should appear in the project root. Replace its content with the following code:

{
    "compileOnSave": true,
    "compilerOptions": {
        "noImplicitAny": false,
        "noEmitOnError": true,
        "removeComments": false,
        "sourceMap": true,
        "target": "es5",
        "outDir": "wwwroot/js"
    },
    "include": [
        "wwwroot/ts/**/*.ts"
    ],
    "exclude": [
        "**/node_modules"
    ]
}

compileOnSave enables compilation on save, while compilerOptions contains all compiler flags. It is easy to recognize the same options we discussed previously:

  • noEmitOnError: No code generation in the case of errors.
  • removeComments: It removes/keeps comments in JavaScript-emitted code.
  • sourceMap: It enables/disables map files.
  • target: It specifies the JavaScript version of the emitted code; in our case, ECMAScript 5.
  • outDir: It specifies where to place the emitted files. If omitted, this is next to their sources.
  • noImplicitAny: It will be discussed later on in this chapter.

Intellisense suggests the allowed options and values, so there is no need to remember their exact names. 

include specifies a list of directory or file patterns to include in the compilation. Each entry is a pattern that may match several directories/files, since entries may contain wildcards: * matches a single name, while ** matches any path. In our case, there is a single pattern that selects all files contained in wwwroot/ts and in all its subdirectories (because of the **), whose extension is .ts. 

exclude specifies a list of patterns to remove from the ones added within other options (for instance, with the include option). In our case, a unique pattern excludes all files contained in any directory called node_modules located at any depth, since these directories usually include JavaScript packages downloaded with the NPM Package Manager.

There is also a files option to add a list of files (just files, not patterns) to the compilation:

"files":[
    "file1.ts",
    "dir/file2.ts",
    ...
]

If no files or include options are specified, the whole project root is taken.

Save tsconfig.json and modify tests.ts. When the file is saved, it should be compiled according to the options contained in tsconfig.json.

Once a tsconfig.json has been added, all options in the TypeScript Build panel of the project properties are disabled:

 

 

 

You may use this panel just to select the TypeScript version; that is, the compiler that is automatically invoked by Visual Studio. All other options must be specified in the configuration file you added. 

 

Basic types


TypeScript primitive types obviously include JavaScript primitive types, namely Boolean, number, string, null, and undefined. However, TypeScript adds a few new primitive types and slightly changes the semantics of primitive types. All type declaration examples in the remainder of the chapter may be tested by adding them to our test.ts file.

TypeScript type system

The TypeScript type system is similar to one of the other object-oriented languages, such as C#. There is a root type called any that is similar, but not completely equivalent, to the C# Object. All primitive types are direct descendants of any, while all complex types, such as the Date type, for instance, descend from the object type, which , in turn, is a direct descendant of any:

var myDate: Date = new Date(); //a Date is a complex type
var myString: string = "this is a string"; //string is a simple type
var myNumber: number = 10; //number is a simple type
var myBoolean: boolean = true; //boolean is a simple type

/* Correct all types descend from any */
var x: any = myDate;
x = myString;
x = myNumber;
x = myBoolean;

/* Correct all comlex types descend from object */
var myObject: object = myDate; 

/* Wrong! Simple types do not descend from object */

myObject = myString; 
myObject = myNumber; 
myObject = myBoolean; 

The last three statements are wrong since primitive types do not descend from objects; the Visual Studio TypeScript editor should immediately signal the following errors:

Errors are underlined in red, and it is enough to hover the mouse over them to see a detailed error message. So, the C# Object is similar to the TypeScript any and not to the TypeScript object.

any and unknown

While any may be considered the TypeScript root type, the semantics of any are quite different from the usual semantics of other languages' root types. In fact, while other languages' root types allow almost no operations on them, any allows all operations. It is a way to prevent any type check, and was conceived this way to allow compatibility of some code chunks with JavaScript. In fact, once something has been declared as any, it may be processed as it can in simple JavaScript, with no preoccupations about compilation-time type checks. 

TypeScript 3.0 also introduces theunknowntype, which behaves more like a usual root type since no operations are allowed on a variable declared asunknown, and all values may be assigned to an unknown variable. Thus, from version 3.0 onward, TypeScript has two root types that can be assigned to each other,anyandunknown.anydisables type checks and allows all operations, whileunknownbehaves like a usual root type. 

 

Strings, numbers, and Booleans

Strings, numbers, and Booleans have the usual JavaScript semantics; there are no integers or decimals, but all numbers are represented by the number type, which is a 64-bit floating point, and Boolean may have just the true and false values:

var myString: string = "this is a string"; //string is a simple type
var myNumber: number = 10; //number is a 64 bit floating point simple type
var myBoolean: boolean = true; //boolean is a simple type whose only values are: true, false. 

 

The null and undefined subtypes

Like in JavaScript, null and undefined denote both the two types and the only values these types may have:

/* correct null type may assume just the null value */
var myNull: null = null;

/* correct undefined type may assume just the undefined value */
var myUndefined: undefined = undefined;

/* Wrong! */
myNull = 10;
myUndefined = 10;

As a default, null and undefined are subtypes of all types (including all custom types defined by the user), so we may assign them to any variable/property.

As in JavaScript, undefined is the implicit value assigned to any object that was not initialized yet:

/* value is undefined since variable was not initialized */
var notInitialized: number;

However, if the strictNullChecks compiler option is explicitly set to true, null and undefined aren't subtypes of all types anymore, so null and undefined become illegal values for all other types.

Let's add this option to our project's tsconfig.ts:

{
    "compileOnSave": true,
    "compilerOptions": {
        "noImplicitAny": false,
"strictNullChecks": true,
        "noEmitOnError": true,
        "removeComments": false,
        "sourceMap": true,
        "target": "es5",
        "outDir": "wwwroot/js"
    },
    "include": [
        "wwwroot/ts/**/*.ts"
    ],
    "exclude": [
        "**/node_modules",
        "**/*.spec.ts"
    ]
}

Then add this code to tests.ts:

/* Wrong! */

var nullCheck: string = null;
var undefinedCheck: string = undefined;

Both declarations will be signaled as wrong, since now null and undefined are illegal values for a string.

If you want a specific string variable to accept null and/or undefined values, you may use another TypeScript feature: union types.

Basics of union types

In order to keep most of the JavaScript flexibility without renouncing compile-time type checks, TypeScript allows the definition of new types as unions of other types. For example:

/* Both statements are correct */
var stringOrNumber: string | number = "Hellow";
stringOrNumber = 10;

A Union Type may assume all values allowed by all its member types, so stringOrNumber may assume both string and number values. Without union types, stringOrNumber should have been declared any, thus renouncing completely any type checks. 

We may mix more than two types in a Union Type so for instance, if we set strictNullChecks but we want a specific string variable to also take nullandundefinedvalues, we may declare it as follows:

var nullCheck: string|null|undefined = null;
var undefinedCheck: string | null | undefined = undefined;

Now both statements are correct. We may also define aliases for union types:

type NullableString = string | null;

type ExtendedString = string | null | undefined;

var nullCheck: NullableString = null;
var undefinedCheck: ExtendedString = undefined;

This way, we factor out their definitions into a single place and avoid tedious repetitions.

Note

If you set the strictNullChecks options and use Union Types and Type aliases to specify when null and undefined are allowed, you have a better compile-time check and you may avoid hard-to-find bugs.

Union Types may also be defined as unions of either numeric constants or string constants. Here is a string constants example:

type fontStype = "Plain" | "Bold" | "Italic" | "Underlined";

var myFontType: fontStype = "Bold"; //Right

myFontType = "Red"; //Wrong

And here is a numeric constants example:

type dice = 1 | 2 | 3 | 4 | 5 | 6;

var myDice: dice =5; //Right

myDice = 7; //Wrong

void

void is a type that can't assume any value except for null or undefined. It make no sense to declare a variable of type void. This type should be used just to declare a function that must return no value:

function sayHello(): void {
    alert("Hello world");
}

If a function whose return value has been declared void actually returns a value along some paths, an error is signaled at compile time:

/* Wrong! */
function sayHelloWrong(): void {
    alert("Hello world");
    return 1;
}

Also, the converse is true; a function whose return type has been declared, say number, but which doesn't return a value along some paths, will trigger a compile-time error:

/* Wrong! */
function wrongNumer(x: number): number {
    if (x > 0) return x;
}

The error may be removed either by adding the missing return statement or by adding void to the return value:

/* Correct! */
function wrongNumer(x: number): number|void {
    if (x > 0) return x;
}

never

never is the type whose values never occur! It is the return type of functions that never return, either because of an endless loop or because they always throw an exception:

/* never return type is explicitly declared */
function error(message: string): never {
    throw message;
}
/* never return type is inferred by compiler */
function alwaysinError() {
    return error("there was an error");
}

You may verify that the compiler automatically infers the never return type of the second function by hovering the mouse over the function name:

 

 

Here is an endless loop:

functionendlessLoop(): never
{     
while (true) 
    {    
        ...
        ... 
    } 
}

Here is a function that may return or may die in an endless loop:

function endlessLoop(x: string): never|number {
    while (true) { 
        if (x == "stop") return 1;
    }
}

Enums

TypeScript also provides C#-style enum types:

enum YesNoAnswer { unknown, yes, no};
var myAnswer: YesNoAnswer = YesNoAnswer.unknown;

Like in C#, enum values are translated into integers. As a default, the first value is assigned 0, the second value 1, and so on. However, the default start value may be changed:

enum YesNoAnswer { unknown=1, yes , no}; //yes=2, no=3

You may also provide default integers for all enum values:

enum YesNoAnswer { unknown=1, yes=3 , no=5};

Integers may also be specified through expressions involving the previously defined variables and values of the same enum: 

enum YesNoAnswer { unknown=1, yes=unknown+2 , no=yes+2};

The TypeScript compiler generates a variable with the same name as the enum and does something like this:

YesNoAnswer = {};
YesNoAnswer["unknown"] = 1;
YesNoAnswer["yes"] = 3;
YesNoAnswer["no"] = 5;

That's why we may use expressions such as YesNoAnswer.unknown to refer to the enum values.

We may also translate the integer associated with each enum value to the string representing its name:

>YesNoAnswer[3]
>"yes"

This is because the TypeScript compiler generates also something like:

YesNoAnswer[1] = "unknown";
YesNoAnswer[3] = "yes";
YesNoAnswer[5] = "no";

If the enum is defined to be constant, the YesNoAnswer variable and all associated properties are not generated, and each occurrence of YesNoAnswer.unknown, YesNoAnswer.yes, and YesNoAnswer.no in the code is replaced by its associated integer at compile time in the generated JavaScript code:

const enum YesNoAnswer { unknown=1, yes=3 , no=5}; 
var myAnswer: YesNoAnswer = YesNoAnswer.unknown;

/* when the enum is const this is wrong*/ 
var valueName: string = YesNoAnswer[1];

However, in this case, expressions like YesNoAnswer[3] are not allowed anymore:

Moreover, all integers defining the constant enum values must be constant the compiler may compute at compile time.

 

Thus, the following is correct:

const enum YesNoAnswer { unknown=1, yes=unknown+2 , no=yes+2};

However, we can't assign the startEnum variable, whose value changes at runtime, to unknown:

var startEnum : number;
...
...
...
const enum YesNoAnswer { unknown=startEnum, yes=unknown+2 , no=yes+2};

Otherwise, we get a compilation error:

Thus, const enum generates less JavaScript code at the price of less flexibility.

enum values may be combined with the bitwise operators &, |, and ~:

const enum YesNoAnswer { unknown = 1, yes = unknown + 2, no = yes + 2 };

var myAnswer: YesNoAnswer = YesNoAnswer.unknown | YesNoAnswer.yes;

Thus, we may define the equivalent of C# bit flags:

const enum TextTransformation {
    None = 0, 
    Bold = 1,
    Italic = Bold << 1, //2
    Underline = Italic << 1, //4
    Overline = Italic << 1, //8
    LineThrough = Overline << 1, // 16
    HasLine = Underline | Overline | LineThrough
}

function HasBold(x: TextTransformation): boolean {
    return (x & TextTransformation.Bold) == TextTransformation.Bold;
}

The HasBold function shows how bit properties may be tested in exactly the same way as in C#.

 

Declarations and scoping 


TypeScript declarations and scoping rules are one of the ECMAScript 6 version of JavaScript, with the only addition of type specifications after the declared objects. Also, TypeScript expressions support some new ECMAScript 6 features, such as string interpolation. In case the JavaScript target is a lower version, all ECMAScript 6-specific features are simulated by the TypeScript transpiler, which automatically generates the equivalent JavaScript code.

Declarations

In the previous example, we have already seen declarations based on the var keyword:

var myString: string;

Declarations may also initialize the variables with expressions involving both constants and other previously defined variables:

var firstName: string = "Francesco";
var surName: string = "Abbruzzese";

function fullName(x: string, y: string, spaces: number): string {
    return x + Array(spaces+1).join(' ') + y;
}

var label: string = fullName(firstName, surName, 3);

A single var statement may also contain several declarations:

var aString: string = "this is a string",
    aNumber: number,
    anInteger: number = 1;

The first and third variables have been initialized, while the second one is still undefined.

TypeScript also supports ECMAScript 6 declarations based on const and let:

const aConstString: string = "this is a string",
    aConstNumber: number=1.5,
    aConstInteger: number = 1;

const has the purpose of defining constants, so variables declared with const are read-only and can't be modified: 

Since constants can't be modified, they must always be initialized in their declarations:

Declarations based on let have the same syntax and almost the same semantics as var-based declarations:

let aString: string = "this is a string",
    aNumber: number,
    anInteger: number = 1;

The difference between var and let is just in the scoping rules. We will analyze scoping rules in detail in a short time.

Obligatoriness of declarations and noImplicitAny

In JavaScript, when an undeclared variable is used, it is automatically declared in global scope, often causing hard-to-find bugs.

For this reason, in TypeScript, variables can be used just after they have been declared:

When a variable is initialized, its type may be automatically inferred, so it may be omitted:

However, when a variable is not initialized, the specification of the variable type is not obligatory! The type assigned to a not initialized variable when the type is not specified explicitly depends on the noImplicitAny compiler option, or, more specifically:

  • When noImplicitAny is false, the variable is assigned the any type.
  • When noImplicitAny is true, the compiler tries to infer the type of the variable from the type of the first expression that is assigned to that variable.

For example, if the configuration file of the TypeScriptTests project sets noImplicitAny to false and we omit type specifications:

var untypedVar;

any is assumed, as you may verify by hovering the mouse over the untypedVar variable:

However, if you set noImplicitAny to true in tsconfig.json, and you slightly change the code:

The number type is automatically inferred because the first value assigned tountypedVar is anumber (namely,1.2). Thus, no error is signaled in the next statement, in which untypedVar is added to a constant number.

Note

Rely on automatic type inference only when a variable is initialized in the declaration so that there are absolutely no doubts about its type. Otherwise, always declare variable types, since hard-to-find bugs might occur in subsequent code modifications.

Variable scoping

Scoping rules are different for the old var and the ECMAScript 6 let and const. All of them have global scope when declared outside any of the functions and blocks:

/*global scope, visible also outside the file they are declared in.*/
var firstName = "Francesco";
var surName = "Abbruzzese";

function fullName(x, y, spaces){
    x + Array(spaces+1).join(' ') + y;
}

However, variables defined with var are visible within the inner function they are defined in, while variables defined with let and const are visible just in the inner block they are defined in:

function scopeTest(x: number) {

    if (x > 0) {
        var result: number = x;
    }
    else {
        result = -x; //correct result is visible within the whole function
    } 
    return result; //correct result is visible within the whole function
}

function scopeTestLet(x: number) {

    if (x > 0) {
        let result: number = x;
    }
    else {
        result = -x; //error result is undefined here
    }
    return result; //error result is undefined here
}

In the case of for loops, if the loop variables are declared with let, different variable instances are defined for each loop iteration:

for (let i = 0; i < 5; i++) {
    setTimeout(function(){ console.log(i) }, 1000);
}

A new variable named i is created at each iteration. So, we have five different variables with the values 0, 1, 2, 3, 4. Due to setTimeout, all variables are logged to the console after 1 second ( 1000 milliseconds), when the for loop is already ended. The five different copies of i remain unchanged and also retain their values after the for loop has ended, so the result in the console will be this:

>0
>1
>2
>3
>4

Now let's see by substituting let with var:

for (var i = 0; i < 5; i++) {
    setTimeout(function(){ console.log(i) }, 1000);
}

Then there would be a unique i, whose scope is the function containing the for loop. Accordingly, after the loop ends, the value of this unique variable would be 5. Therefore, since all five occurrences of the setTimeout statement capture this variable, and since all values are logged after the end of the for loop, the result in the console will be this:

>5
>5
>5
>5
>5

As in C#, variables may always be redefined outside their scope, since variables defined this way are considered different:

for (let i = 0; i < 5; i++) {
    setTimeout(function(){ console.log(i) }, 1000);
}
let i: string ="this is a string";

In a difference from C#, a new variable with the same name may also appear in the same scope. In this case, the newer definition overrides the previous one:

var firstName = "francesco";
var firstName = "peter"; //correct, new definition overrides previous one.

However, overrides are allowed only if the two variables have the same type:

Expressions – type assertions, and string interpolation

The syntax of TypeScript expressions is exactly the same as the syntax of JavaScript expressions. This syntax is also identical to the expression syntax of other languages, such as C# and C++. However, TypeScript contains one more unary operator: the type assertion operator. This operator doesn't generate any JavaScript code; it simply replaces the type inferred by the TypeScript compiler with a type assertion provided by the developer:

function tryConvertNumber(x: string): string | number {
    var res = parseFloat(x);
    if (isNaN(res)) return x;
    else return res;
}
let numberAsAString: string = "5";

let newNumber: number = <number>tryConvertNumber(numberAsAString);

In this case, the compiler is not able to infer that the result of the function call is actually a number, so the developer, who knows it from the logic of the code, avoids a type error by asserting the right type. Type assertions also have an as syntax:

let newNumber: number = tryConvertNumber(letbumberAsAString) as number;

The two syntaxes are completely equivalent. It is worth pointing out that while syntax is very similar to C#, semantics are completely different. In fact, while C# attempts an actual type conversion; TypeScript does not attempt any type conversion, it just replaces its inferred type with the one provided by the developer.

 

String interpolation is another expression enhancement provided by TypeScript. Actually, it is already part of ECMAScript 6, but TypeScript also provides it when targeting lower JavaScript versions. String interpolation is also available in C# from version 6.0 onward and is a kind of enhanced string format:

let person = {
    name: "francesco",
    surname: "abbruzzese",
    title: "Mr."
};

let hello: string = `Hello ${person.title + ' ' + person.name} ${person.name}`;

String templates are enclosed between backticks, `, instead of the usual quotes with strings. Each ${} may contain any expression that evaluates to a string. The content of each ${} is evaluated and replaces the ${} in the final string. In our example, the final string is this:

>Hello Mr. francesco abbruzzese
 

Summary


TypeScript enhances JavaScript with types, and with features that are only available in the more recent ECMAScript standards. You may install the TypeScript SDK in Visual Studio to take advantage of the same user interface you are used to with C#, or you may install it with Node.js. Adding TypeScript to your ASP.NET projects is straightforward; it is enough to add .ts files to it. Compiler options may be specified within Visual Studio project options, or with a specific JSON configuration file. According to the compiler settings, JavaScript files are generated either on saving the respective .ts files or when the project is built, and may be placed in your Views in the usual way. 

TypeScript declarations are very similar to JavaScript declarations, the only differences being that they are obligatory and are the specifications of types. Simple types are the same as JavaScript, with only the addition ofany, unknown, void, never, and the C#-likeenum.

Scoping rules include let and const ECMAScript 6 block-level scoping, together with the usual var-based scoping. 

TypeScript enhances JavaScript expressions with type assertions and ECMAScript 6 string interpolation.

 

 

 

Questions


  1. What are the main benefits of adding types to JavaScript?
  2. Is it possible to get the type of a TypeScript variable at runtime? How?
  3. At the moment, is there any chance the TypeScript team will add something like C# operator overloading so users may define custom behavior for + and - when applied to their custom types? Why?
  4. Is there a way to define a variable that might contain both numbers and strings? What is the best way to do it?
  5. What is the return type of a function that returns no value?
  6. What is the scope of a variable that has not been declared?
  7. How many instances of the counter variable are generated by a var-based for loop that iterates 100 times? How many instances are generated by the let version of the same loop?
  8. Is this assertion true: TypeScript string variables are always allowed to have a null value? Explain.
  9. What is the most space-efficient way to define an enum (the one that generates the least code and requires the least runtime memory)?
  10. How do you declare that the values of an enum are C#-like bit flags?

 

 

Further reading


This chapter, as does the remainder of the book, assumes a minimum familiarity with both ASP.NET Core and JavaScript basics. To learn about them, you may refer to Learning ASP.NET Core 2.0 (https://www.packtpub.com/application-development/javascript-and-jquery-7-days-video), and to Microsoft's official documentation (https://docs.microsoft.com/en-US/aspnet/core/?view=aspnetcore-2.1) for ASP.NET Core. Also, refer to JavaScript (and jQuery) in 7 Days (https://www.packtpub.com/application-development/javascript-and-jquery-7-days-video) to learn the basics of JavaScript and jQuery.

To learn more about TypeScript's design goals, please refer to the official design goals stated by the TypeScript development team: https://github.com/Microsoft/TypeScript/wiki/TypeScript-Design-Goals.

About the Author

  • Francesco Abbruzzese

    Francesco Abbruzzese is an author of the MVC Controls Toolkit. He has also contributed to the diffusion and evangelization of the Microsoft web stack since the first version of ASP.NET MVC through tutorials, articles, and tools.

    He writes about .NET and client-side technologies in his blog, dotnet-programming, and various online magazines/blogs. Now, his company, mvc-controls, implements and offers web applications, AI software, SAS products, tools, and services for web technologies associated with the Microsoft stack.

    He moved from AI systems, where he implemented one of the first decision support systems for banks and financial institutions, to the video games arena, with top-ten titles such as Puma Street Soccer.

    Browse publications by this author

Latest Reviews

(3 reviews total)
schade nicht empfehlenswert
What I've been able to read through so far has been good.
Me ha sorprendido gratamente. Bien explicado y con detalles suficientes casi siempre. El curso está en general bien planeado. Supera en su didáctica a otros varios libros que he leído sobre el mismo tema. Lo encuentro muy bueno para niveles básico e intermedio.

Recommended For You

Book Title
Access this book, plus 8,000 other titles for FREE
Access now