Learning TypeScript - Sample Chapter
Learning TypeScript - Sample Chapter
ee
$ 49.99 US
31.99 UK
P U B L I S H I N G
pl
C o m m u n i t y
E x p e r i e n c e
D i s t i l l e d
Remo H. Jansen
Learning TypeScript
Learning TypeScript
Sa
m
Learning TypeScript
Exploit the features of TypeScript to develop and maintain
captivating web applications with ease
Remo H. Jansen
He is originally from Seville, Spain, but currently lives in Dublin, Ireland, where
he has a full-time job in the financial services industry. Remo has been working
on large-scale JavaScript applications for the last few years, from flight booking
engines to investment and portfolio management solutions.
Remo is an active member of the TypeScript community. He is the organizer of
the Dublin TypeScript Meet-up and the creator of InversifyJS (an inversion of
control container for TypeScript applications) and AtSpy (a test spies framework
for TypeScript applications). He also writes a blog about TypeScript and other
web technologies at http://blog.wolksoftware.com/.
Remo has previously worked as a technical reviewer on Mastering TypeScript
written by Nathan Rozentals and published by Packt Publishing.
If you wish to contact him, you can do so at http://www.remojansen.com/.
Preface
Over the past few years, the JavaScript code base of an average web application
has been exponentially growing. However, the current JavaScript specification
(ECMAScript 5 or ES5) was designed several years ago and lacks some features
that are necessary to cope with the complexity that we can find in large-scale
JavaScript applications today. As a result of these missing features, maintainability
problems have arisen.
The upcoming JavaScript version (ECMAScript 6 or ES6) is meant to solve some
of the maintainability issues of JavaScript, but its implementation is in progress
and many incompatible web browsers are still in use today. For these reasons,
the wide adoption of the ES6 specification is expected to be a slow process.
Microsoft spent two years developing TypeScript with the goal of resolving the
maintainability and scalability problems of JavaScript and publicly announced it
in October 2012:
"We designed TypeScript to meet the needs of the JavaScript programming
teams that build and maintain large JavaScript programs. TypeScript helps
programming teams to define interfaces between software components and
to gain insight into the behavior of existing JavaScript libraries. TypeScript
also enables teams to reduce naming conflicts by organizing their code into
dynamically loadable modules. Typescript's optional type system enables
JavaScript programmers to use highly-productive development tools and
practices: static checking, symbol-based navigation, statement completion,
and code re-factoring."
- TypeScript Language Specification 1.0
Preface
Preface
Introducing TypeScript
This book focuses on TypeScript's object-oriented nature and how it can help you
to write better code. Before diving into the object-oriented programing features of
TypeScript, this chapter will give you an overview of the history behind TypeScript
and introduce you to some of the basics.
In this chapter, you will learn about the following concepts:
Type annotations
Operators
Functions
Classes
Interfaces
Modules
[1]
Introducing TypeScript
Design goals
In the following points, you will find the main design goals and architectural
decisions that shaped the way the TypeScript programming language looks
like today:
Chapter 1
Align with the current and future ECMAScript proposals. TypeScript is not
just compatible with the existing JavaScript code; it will also potentially be
compatible with future versions of JavaScript. The majority of Typescript's
additional features are based on the future ECMAScript proposals; this
means many TypeScript files will eventually become valid JavaScript files.
TypeScript components
The TypeScript language is internally divided into three main layers. Each of these
layers is, in turn, divided into sublayers or components. In the following diagram,
we can see the three layers (green, blue, and orange) and each of their internal
components (boxes):
[3]
Introducing TypeScript
We don't need to go into any more detail as understanding how the TypeScript
compiler works is out of the scope of this book; however, if you wish to learn more
about this topic, refer to the TypeScript language specification, which can be found
online at http://www.typescriptlang.org/.
[4]
Chapter 1
In the preceding screenshot, you will be able to use the text editor on the left-hand
side to write your TypeScript code. The code is automatically compiled to JavaScript
and the output code will be inserted in the text editor located on the right-hand side
of the screen. If your TypeScript code is invalid, the JavaScript code on the righthand side will not be refreshed.
Alternatively, if you prefer to be able to work offline, you can download and install the
TypeScript compiler. If you work with Visual Studio, you can download the official
TypeScript extension (version 1.5 beta) from https://visualstudiogallery.msdn.
microsoft.com/107f89a0-a542-4264-b0a9-eb91037cf7af. If you are working
with Visual Studio 2015, you don't need to install the extension as Visual Studio 2015
includes TypeScript support by default.
If you use a different code editor or you use the OS X or Linux operating systems,
you can download an npm module instead. Don't worry if you are not familiar with
npm. For now, you just need to know that it stands for Node Package Manager and
is the default Node.js package manager.
[5]
Introducing TypeScript
In order to be able to use npm, you will need to first install Node.js in your
development environment. You will be able to find the Node.js installation
files on the official website at https://nodejs.org/.
Once you have installed Node.js in your development environment, you will be able
to run the following command in a console or terminal:
npm install -g typescript
OS X users need to use the sudo command when installing global (-g) npm packages.
The sudo command will prompt for user credentials and install the package using
administrative privileges:
sudo npm install -g typescript
Create a new file named test.ts and add the following code to it:
var t : number = 1;
Save the file into a directory of your choice and once you have saved the file open
the console, select the directory where you saved the file, and execute the following
command:
tsc test.ts
The tsc command is a console interface for the TypeScript compiler. This command
allows you to compile your TypeScript files into JavaScript files. The compiler features
many options that will be explored in the upcoming chapters of this book.
In the preceding example, we used the tsc command to transform the test.ts file
into a JavaScript file.
If everything goes right, you will find a file named test.js in the same directory in
which the test.ts file was located. Now, you know how to compile your TypeScript
code into JavaScript and we can start learning about the TypeScript features.
You will be able to learn more about editors and other tools in
Chapter 2, Automating Your Development Workflow.
[6]
Chapter 1
Types
As we have already learned, TypeScript is a typed superset of JavaScript. TypeScript
added optional static type annotations to JavaScript in order to transform it into a
strongly typed programming language. The optional static type annotations are used
as constraints on program entities such as functions, variables, and properties so that
compilers and development tools can offer better verification and assistance (such as
IntelliSense) during software development.
Strong typing allows the programmer to express his intentions in his code, both to
himself and to others in the development team.
Typescript's type analysis occurs entirely at compile time and adds no runtime
overhead to program execution.
counter;
counter = 0;
counter : number;
counter : number = 0;
//
//
//
//
As you can see, the type of the variable is declared after the name, this style of
type notation is based on type theory and helps to reinforce the idea of types being
optional. When no type annotations are available, TypeScript will try to guess the
type of the variable by examining the assigned values. For example, in the second
line in the preceding code snippet, we can see that the variable counter has been
identified as a numeric variable because the numeric value 0 was assigned as its
value. This process in which types are automatically detected is known as Type
inference, when a type cannot be inferred the especial type any is used as the
type of the variable.
[7]
Introducing TypeScript
Description
Boolean
Whereas the string and number data types can have a virtually unlimited
number of different values, the Boolean data type can only have two.
They are the literals true and false. A Boolean value is a truth value;
it specifies whether the condition is true or not.
var isDone: boolean = false;
Number
String
You use the string data type to represent text in TypeScript. You include
string literals in your scripts by enclosing them in single or double quotation
marks. Double quotation marks can be contained in strings surrounded
by single quotation marks, and single quotation marks can be contained in
strings surrounded by double quotation marks.
var name: string = "bob";
name = 'smith';
Array
Enum
[8]
Chapter 1
Data Type
Description
Any
The any type is used to represent any JavaScript value. A value of the any
type supports the same operations as a value in JavaScript and minimal
static type checking is performed for operations on any values.
var notSure: any = 4;
notSure = "maybe a string instead";
notSure = false; // okay, definitely a boolean
The any type is a powerful way to work with existing JavaScript, allowing
you to gradually opt in and opt out of type checking during compilation.
The any type is also handy if you know some part of the type, but perhaps
not all of it. For example, you may have an array but the array has a mix of
different types:
var list:any[] = [1, true, "free"];
list[1] = 100;
Void
The opposite in some ways to any is void, the absence of having any type
at all. You will see this as the return type of functions that do not return
a value.
function warnUser(): void {
alert("This is my warning message");
}
JavaScript's primitive types also include undefined and null. In JavaScript, undefined
is a property in the global scope that is assigned as a value to variables that have
been declared but have not yet been initialized. The value null is a literal (not a
property of the global object). It can be assigned to a variable as a representation of
no value.
var TestVar;
// variable is declared but not initialized
alert(TestVar);
// shows undefined
alert(typeof TestVar); // shows undefined
var TestVar = null;
// variable is declared and value null is
assigned as value
alert(TestVar);
// shows null
alert(typeof TestVar); // shows object
Since null or undefined cannot be used as types, both the variable declarations in
the preceding code snippet are invalid.
[9]
Introducing TypeScript
Variables declared with var are scoped to the nearest function block (or global, if
outside a function block).
Variables declared with let are scoped to the nearest enclosing block (or global if
outside any block), which can be smaller than a function block.
The const keyword creates a constant that can be global or local to the block in
which it is declared. This means that constants are block scoped. You will learn
more about scopes in Chapter 5, Runtime.
The let and const keywords have been available since the
release of TypeScript 1.4 but only when the compilation target
is ECMAScript 6. However, they will also work when targeting
ECMAScript 3 and ECMAScript 5 once TypeScript 1.5 is released.
Union types
TypeScript allows you to declare union types:
var path : string[]|string;
path = '/temp/log.xml';
path = ['/temp/log.xml', '/temp/errors.xml'];
path = 1; // Error
Union types are used to declare a variable that is able to store a value of two or more
types. In the preceding example, we have declared a variable named path that can
contain a single path (string), or a collection of paths (array of string). In the example,
we have also set the value of the variable. We assigned a string and an array of
strings without errors; however, when we attempted to assign a numeric value, we
got a compilation error because the union type didn't declare a number as one of the
valid types of the variable.
[ 10 ]
Chapter 1
Type guards
We can examine the type of an expression at runtime by using the typeof or
instanceof operators. The TypeScript language service looks for these operators
and will change type inference accordingly when used in an if block:
var x: any = { /* ... */ };
if(typeof x === 'string') {
console.log(x.splice(3, 1)); // Error, 'splice' does not exist
on 'string'
}
// x is still any
x.foo(); // OK
In the preceding code snippet, we have declared an x variable of type any. Later, we
check the type of x at runtime by using the typeof operator. If the type of x results
to be string, we will try to invoke the method splice, which is supposed to a member
of the x variable. The TypeScript language service is able to understand the usage of
typeof in a conditional statement. TypeScript will automatically assume that x must
be a string and let us know that the splice method does not exist on the type string.
This feature is known as type guards.
Type aliases
TypeScript allows us to declare type aliases by using the type keyword:
type
type
type
type
PrimitiveArray = Array<string|number|boolean>;
MyNumber = number;
NgScope = ng.IScope;
Callback = () => void;
Type aliases are exactly the same as their original types; they are simply alternative
names. Type aliases can help us to make our code more readable but it can also lead
to some problems.
If you work as part of a large team, the indiscriminate creation of aliases can lead
to maintainability problems. In the book, Maintainable JavaScript, Nicholas C. Zakas,
the author recommends to avoid modifying objects you don't own. Nicholas was
talking about adding, removing, or overriding methods in objects that have not
been declared by you (DOM objects, BOM objects, primitive types, and third-party
libraries) but we can apply this rule to the usage of aliases as well.
[ 11 ]
Introducing TypeScript
Ambient declarations
Ambient declaration allows you to create a variable in your TypeScript code that will
not be translated into JavaScript at compilation time. This feature was designed to
facilitate integration with the existing JavaScript code, the DOM (Document Object
Model), and BOM (Browser Object Model). Let's take a look at an example:
customConsole.log("A log entry!");
// error
If you try to call the member log of an object named customConsole, TypeScript will
let us know that the customConsole object has not been declared:
// Cannot find name 'customConsole'
This is not a surprise. However, sometimes we want to invoke an object that has not
been defined, for example, the console or window objects.
console.log("Log Entry!");
var host = window.location.hostname;
When we access DOM or BOM objects, we don't get an error because these objects
have already been declared in a special TypeScript file known as declaration files.
You can use the declare operator to create an ambient declaration.
In the following code snippet, we will declare an interface that is implemented by the
customConsole object. We then use the declare operator to add the customConsole
object to the scope:
interface ICustomConsole {
log(arg : string) : void;
}
declare var customConsole : ICustomConsole;
Chapter 1
Arithmetic operators
There following arithmetic operators are supported by the TypeScript programming
language. In order to understand the examples, you must assume that variable A
holds 10 and variable B holds 20.
Operator
Description
Example
A + B will give 30
B / A will give 2
B % A will give 0
++
--
Comparison operators
The following comparison operators are supported by the TypeScript language. In
order to understand the examples, you must assume that variable A holds 10 and
variable B holds 20.
Operator
Description
Example
==
(A == B) is
false. A == "10"
is true.
===
A === B is
false. A ===
"10" is false.
!=
(A != B) is true.
[ 13 ]
Introducing TypeScript
Operator
Description
Example
>
(A > B) is false.
<
(A < B) is true.
>=
(A >= B) is
false.
<=
(A <= B) is
true.
Logical operators
The following logical operators are supported by the TypeScript language. In order
to understand the examples, you must assume that variable A holds 10 and variable
B holds 20.
Operator
Description
Example
&&
(A && B) is true.
||
(A || B) is true.
Bitwise operators
The following bitwise operators are supported by the TypeScript language. In order
to understand the examples, you must assume that variable A holds 2 and variable B
holds 3.
Operator
Description
Example
&
(A & B) is 2
(A | B) is 3.
[ 14 ]
Chapter 1
Operator
Description
Example
(A ^ B) is 1.
(~B) is -4
<<
(A << 1) is 4
>>
(A >> 1) is 1
>>>
(A >>> 1) is 1
Assignment operators
The following assignment operators are supported by the TypeScript language.
Operator
Description
Example
C = A + B will
assign the value
of A + B into C
+=
C += A is
equivalent to C
=C+A
[ 15 ]
Introducing TypeScript
Operator
Description
Example
-=
C -= A is
equivalent to C
=C-A
*=
C *= A is
equivalent to C
=C*A
/=
C /= A is
equivalent to C
=C/A
%=
C %= A is
equivalent to C
=C%A
Chapter 1
}
else {
alert("Is NOT valid!");
}
The preceding code snippet declares a variable of type Boolean and name isValid.
Then it checks whether the variable or expression on the left-hand side of the operator
? is equal to true.
If the statement turns out to be true, the expression on the left-hand side of the
character will be executed and the message Is valid! will be assigned to the
message variable.
On the other hand, if the statement turns out to be false, the expression on the
right-hand side of the operator will be executed and the message, Is NOT valid!
will be assigned to the message variable.
Finally, the value of the message variable is displayed on the screen.
AlertLevel. Inside the function, we will generate an array of strings to store e-mail
addresses and execute a switch structure. Each of the options of the enumeration is
a case in the switch structure:
enum AlertLevel{
info,
warning,
error
}
[ 17 ]
Introducing TypeScript
function getAlertSubscribers(level : AlertLevel){
var emails = new Array<string>();
switch(level){
case AlertLevel.info:
emails.push("cst@domain.com");
break;
case AlertLevel.warning:
emails.push("development@domain.com");
emails.push("sysadmin@domain.com");
break;
case AlertLevel.error:
emails.push("development@domain.com");
emails.push("sysadmin@domain.com");
emails.push("management@domain.com");
break;
default:
throw new Error("Invalid argument!");
}
return emails;
}
getAlertSubscribers(AlertLevel.info); // ["cst@domain.com"]
getAlertSubscribers(AlertLevel.warning); //
["development@domain.com", "sysadmin@domain.com"]
The value of the level variable is tested against all the cases in the switch. If the variable
matches one of the cases, the statement associated with that case is executed. Once the
case statement has been executed, the variable is tested against the next case.
Once the execution of the statement associated to a matching case is finalized, the
next case will be evaluated. If the break keyword is present, the program will not
continue the execution of the following case statement.
If no matching case clause is found, the program looks for the optional default
clause, and if found, it transfers control to that clause and executes the associated
statements.
If no default clause is found, the program continues execution at the statement
following the end of switch. By convention, the default clause is the last clause,
but it does not have to be so.
[ 18 ]
Chapter 1
In a while expression, the operation will take place only if the requirement is satisfied.
Unlike the while loop, the do-while expression will execute at least once regardless
of the requirement value as the operation will take place before checking if a certain
requirement is satisfied or not.
[ 19 ]
Introducing TypeScript
Output:
"a = 1"
"b = 2"
"c = 3"
The following code snippet will go up in the prototype chain, also enumerating the
inherited properties. The for-in statement iterates the entire prototype chain, also
enumerating the inherited properties. When you want to enumerate only the object's
own properties (the ones that aren't inherited), you can use the hasOwnProperty
method:
for (var key in obj) {
if (obj.hasOwnProperty(prop)) {
// prop is not inherited
}
}
The preceding code snippet contains a for statement, it starts by declaring the
variable i and initializing it to 0. It checks whether i is less than 9, performs the two
succeeding statements, and increments i by 1 after each pass through the loop.
[ 20 ]
Chapter 1
Functions
Just as in JavaScript, TypeScript functions can be created either as a named function
or as an anonymous function. This allows us to choose the most appropriate
approach for an application, whether we are building a list of functions in an
API or a one-off function to hand over to another function.
// named function
function greet(name? : string) : string {
if(name){
return "Hi! " + name;
}
else
{
return "Hi!";
}
}
// anonymous function
var greet = function(name? : string) : string {
if(name){
return "Hi! " + name;
}
else
{
return "Hi!";
}
}
As we can see in the preceding code snippet, in TypeScript we can add types to each
of the parameters and then to the function itself to add a return type. TypeScript can
infer the return type by looking at the return statements, so we can also optionally
leave this off in many cases.
There is an alternative function syntax, which uses the arrow (=>) operator after the
functions return type and skips the usage of the function keyword.
var greet = (name : string) : string => {
if(name){
return "Hi! " + name;
}
else
{
return "Hi! my name is " + this.fullname;
}
};
[ 21 ]
Introducing TypeScript
The functions declared using this syntax are commonly known as arrow functions.
Let's return to the previous example in which we were assigning an anonymous
function to the greet variable. We can now add the type annotations to the greet
variable to match the anonymous function signature.
var greet : (name : string) => string = function(name : string) :
string {
if(name){
return "Hi! " + name;
}
else
{
return "Hi!";
}
};
Keep in mind that the arrow function (=>) syntax changes the way the
this operator works when working with classes. We will learn more
about this in the upcoming chapters.
Now you know how to add type annotations to force a variable to be a function with
a specific signature. The usage of this kind of annotations is really common when we
use a call back (functions used as an argument of another function).
function sume(a : number, b : number, callback : (result:number)
=> void){
callback(a+b);
}
In the preceding example, we are declaring a function named sume that takes two
numbers and a callback as a function. The type annotations will force the callback
to return void and take a number as its only argument.
We will focus on functions in Chapter 3, Working with Functions.
Classes
ECMAScript 6, the next version of JavaScript, adds class-based object orientation
to JavaScript and, since TypeScript is based on ES6, developers are allowed to use
class-based object orientation today, and compile them down to JavaScript that
works across all major browsers and platforms, without having to wait for the next
version of JavaScript.
[ 22 ]
Chapter 1
In the preceding example, we have declared a new class Character. This class has
three members: a property called fullname, a constructor, and a method greet.
When we declare a class in TypeScript, all the methods and properties are public
by default.
You'll notice that when we refer to one of the members of the class (from within
itself) we prepend the this operator. The this operator denotes that it's a member
access. In the last lines, we construct an instance of the Character class using a new
operator. This calls into the constructor we defined earlier, creating a new object with
the Character shape, and running the constructor to initialize it.
TypeScript classes are compiled into JavaScript functions in order to achieve
compatibility with ECMAScript 3 and ECMAScript 5.
We will learn more about classes and other object-oriented programming
concepts in Chapter 4, Object-Oriented Programming with TypeScript.
[ 23 ]
Introducing TypeScript
Interfaces
In TypeScript, we can use interfaces to enforce that a class follow the specification in
a particular contract.
interface LoggerInterface{
log(arg : any) : void;
}
class Logger implements LoggerInterface{
log(arg){
if(typeof console.log === "function"){
console.log(arg);
}
else
{
alert(arg);
}
}
}
[ 24 ]
Chapter 1
Namespaces
Namespaces, also known as internal modules, are used to encapsulate features and
objects that share a certain relationship. Namespaces will help you to organize your
code in a much clearer way. To declare a namespace in TypeScript, you will use the
namespace and export keywords.
namespace Geometry{
interface VectorInterface {
/* ... */
}
export interface Vector2dInterface {
/* ... */
}
export interface Vector3dInterface {
/* ... */
}
export class Vector2d implements VectorInterface,
Vector2dInterface {
/* ... */
}
export class Vector3d implements VectorInterface,
Vector3dInterface {
/* ... */
}
}
var vector2dInstance : Geometry.Vector2dInterface = new
Geometry.Vector2d();
var vector3dInstance : Geometry.Vector3dInterface = new
Geometry.Vector3d();
[ 25 ]
Introducing TypeScript
[ 26 ]
Chapter 1
3D engines are complex software solutions, and as a developer, you are much more
likely to use a third-party 3D engine than create your own. For this reason, it is
important to understand that TypeScript will not only help you to develop large-scale
applications, but also to work with large-scale applications. In the following code
snippet, we will use the module declared earlier to create a Vector2d instance:
var vector : Geometry.Vector2dInterface = new
Geometry.Vector2d(2,3);
vector.normalize();
vector.toArray(function(vectorAsArray : number[]){
alert(' x :' + vectorAsArray[0] + ' y : '+ vectorAsArray[1]);
});
The type checking and IntelliSense features will help us create a Vector2d instance,
normalize its value, and convert it into an array to finally show its value on screen
with ease.
Summary
In this chapter, you have learned about the purposes of TypeScript. You have also
learned about some of the design decisions made by the TypeScript engineers at
Microsoft.
Towards the end of this chapter, you learned a lot about the basic building blocks of
a TypeScript application .You started to write some TypeScript code for the first time
and you can now work with type annotations, variables and primitive data types,
operators, flow control statements, functions, and classes.
In the next chapter, you will learn how to automate your development workflow.
[ 27 ]
www.PacktPub.com
Stay Connected: