Book Image

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

By : Francesco Abbruzzese
5 (1)
Book Image

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

5 (1)
By: Francesco Abbruzzese

Overview of 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.
Table of Contents (16 chapters)

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 to untypedVar is a number (namely, 1.2). Thus, no error is signaled in the next statement, in which untypedVar is added to a constant number.

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