CHAPTER 1

image

TypeScript Language Features

What if we could strengthen JavaScript with the things that are missing for large scale application development, like static typing, classes [and] modules . . . that’s what TypeScript is about.

—Anders Hejlsberg

TypeScript is a superset of JavaScript. That means that the TypeScript language includes the entire JavaScript language plus a collection of useful additional features. This is in contrast to the various subsets of JavaScript and the various lint tools that seek to reduce the available features to create a smaller language with fewer surprises. This chapter will introduce you to the extra language features, starting with simple type annotations and progressing to more advanced features and structural elements of TypeScript. This chapter doesn’t cover the features included in the ECMAScript 5 language specification so if you need a refresher on JavaScript take a look at Appendix 1.

The important thing to remember is that all of the standard control structures found in JavaScript are immediately available within a TypeScript program. This includes:

  • Control flows
  • Data types
  • Operators
  • Subroutines

The basic building blocks of your program will come from JavaScript, including if statements, switch statements, loops, arithmetic, logical tests, and functions. This is one of the key strengths of TypeScript—it is based on a language (and a family of languages) that is already familiar to a vast and varied collection of programmers. JavaScript is thoroughly documented not only in the ECMA-262 specification, but also in books, on developer network portals, forums, and question-and-answer websites.

Each of the language features discussed in this chapter has short, self-contained code examples that put the feature in context. For the purposes of introducing and explaining features, the examples are short and to the point; this allows the chapter to be read end-to-end. However, this also means you can refer back to the chapter as a reference later on. Once you have read this chapter, you should know everything you will need to understand the more complex examples described throughout the rest of the book.

JavaScript Is Valid TypeScript

Before we find out more about the TypeScript syntax, it is worth stressing this important fact: All JavaScript is valid TypeScript, with just a small number of exceptions, which are explained below. You can take existing JavaScript code, add it to a TypeScript file, and all of the statements will be valid. There is a subtle difference between valid code and error-free code in TypeScript; because, although your code may work, the TypeScript compiler will warn you about any potential problems it has detected.

If you transfer a JavaScript listing into a TypeScript file you may receive errors or warnings even though the code is considered valid. A common example comes from the dynamic type system in JavaScript wherein it is perfectly acceptable to assign values of different types to the same variable during its lifetime. TypeScript detects these assignments and generates errors to warn you that the type of the variable has been changed by the assignment. Because this is a common cause of errors in a program, you can correct the error by creating separate variables, by performing a type assertion, or by making the variable dynamic. There is further information on type annotations later in this chapter, and the type system is discussed in detail in Chapter 2.

Unlike some compilers that will only create output where no compilation errors are detected, the TypeScript compiler will still attempt to generate sensible JavaScript code. The code shown in Listing 1-1 generates an error, but the JavaScript output is still produced. This is an admirable feature, but as always with compiler warnings and errors, you should correct the problem in your source code and get a clean compilation. If you routinely ignore warnings, your program will eventually exhibit unexpected behavior. In some cases, your listing may contain errors that are so severe the TypeScript compiler won’t be able to generate the JavaScript output.

Listing 1-1. Using JavaScript’s “with” statement

// Not using with
var radius = 4;
var area = Math.PI * radius * radius;

// Using with
var radius = 4;
with (Math) {
    var area = PI * radius * radius;
}

Image Caution  The only exceptions to the “all JavaScript is valid TypeScript” rule are the with statement and vendor specific extensions, such as Mozilla’s const keyword.

The JavaScript with statement in Listing 1-1 shows two examples of the same routine. Although the first calls Math.PI explicitly, the second uses a with statement, which adds the properties and functions of Math to the current scope. Statements nested inside the with statement can omit the Math prefix and call properties and functions directly, for example the PI property or the floor function.

At the end of the with statement, the original lexical scope is restored, so subsequent calls outside of the with block must use the Math prefix.

The with statement is not allowed in strict mode in ECMAScript 5 and in ECMAScript 6 classes and modules will be treated as being in strict mode by default. TypeScript treats with statements as an error and will treat all types within the with statement as dynamic types. This is due to the following:

  • The fact it is disallowed in strict mode.
  • The general opinion that the with statement is dangerous.
  • The practical issues of determining the identifiers that are in scope at compile time.

So with these minor exceptions to the rule in mind, you can place any valid JavaScript into a TypeScript file and it will be valid TypeScript. As an example, here is the area calculation script transferred to a TypeScript file.

Image Note  The ECMAScript 6 specification, also known as “ES6 Harmony,” represents a substantial change to the JavaScript language. The specification is still under development at the time of writing.

In Listing 1-2, the statements are just plain JavaScript, but in TypeScript the variables radius and area will both benefit from type inference. Because radius is initialized with the value 4, it can be inferred that the type of radius is number. With just a slight increase in effort, the result of multiplying Math.PI, which is known to be a number, with the radius variable that has been inferred to be a number, it is possible to infer the type of area is also a number.

Listing 1-2. Transferring JavaScript in to a TypeScript file

var radius = 4;
var area = Math.PI * radius * radius;

With type inference at work, assignments can be checked for type safety. Figure 1-1 shows how an unsafe assignment is detected when a string is assigned to the radius variable. There is a more detailed explanation of type inference in Chapter 2.

9781430267911_Fig01-01.jpg

Figure 1-1. Static type checking

Variables

TypeScript variables must follow the JavaScript naming rules. The identifier used to name a variable must satisfy the following conditions.

The first character must be one of the following:

  • an uppercase letter
  • a lowercase letter
  • an underscore
  • a dollar sign
  • a Unicode character from categories—Uppercase letter (Lu), Lowercase letter (Ll), Title case letter (Lt), Modifier letter (Lm), Other letter (Lo), or Letter number (Nl)

Subsequent characters follow the same rule and also allow the following:

  • numeric digits
  • a Unicode character from categories—Non-spacing mark (Mn), Spacing combining mark (Mc), Decimal digit number (Nd), or Connector punctuation (Pc)
  • the Unicode characters U+200C (Zero Width Non-Joiner) and U+200D (Zero Width Joiner)

You can test a variable identifier for conformance to the naming rules using the JavaScript variable name validator by Mathias Bynens.

http://mothereff.in/js-variables

Image Note  The availability of some of the more exotic characters can allow some interesting identifiers. You should consider whether this kind of variable name causes more problems than it solves. For example this is valid JavaScript: var img = 'Dignified';

Variables are functionally scoped. If they are declared at the top level of your program they are available in the global scope. You should minimize the number of variables in the global scope to reduce the likelihood of naming collisions. Variables declared inside of functions, modules, or classes are available in the context they are declared as well as in nested contexts.

In JavaScript it is possible to create a global variable by declaring it without the var keyword. This is commonly done inadvertently when the var keyword is accidentally missed; it is rarely done deliberately. In a TypeScript program, this will cause an error, which prevents a whole category of hard to diagnose bugs in your code. Listing 1-3 shows a valid JavaScript function that contains an implicit global variable, for which TypeScript will generate a "Could not find symbol" error. This error can be corrected either by adding the var keyword, which would make the variable locally scoped to the addNumbers function, or by explicitly declaring a variable in the global scope.

Listing 1-3. Implicit global variable

function addNumbers(a, b) {
    // missing var keyword
    total = a + b;
    return total;
}

Types

TypeScript is optionally statically typed; this means that types are checked automatically to prevent accidental assignments of invalid values. It is possible to opt out of this by declaring dynamic variables. Static type checking reduces errors caused by accidental misuse of types. You can also create types to replace primitive types to prevent parameter ordering errors, as described in Chapter 2. Most important, static typing allows development tools to provide intelligent autocompletion.

Figure 1-2 shows autocompletion that is aware of the variable type, and supplies a relevant list of options. It also shows the extended information known about the properties and methods in the autocompletion list. Contextual autocompletion is useful enough for primitive types—but most reasonable integrated development environments can replicate simple inference even in a JavaScript file. However, in a program with a large number of custom types, modules, and classes, the deep type knowledge of the TypeScript Language Service means you will have sensible autocompletion throughout your entire program.

9781430267911_Fig01-02.jpg

Figure 1-2. TypeScript autocompletion

Type Annotations

Although the TypeScript language service is expert at inferring types automatically, there are times when it isn’t able to determine the type. There will also be times where you will wish to make a type explicit either for safety or readability. In all of these cases, you can use a type annotation to specify the type.

For a variable, the type annotation comes after the identifier and is preceded by a colon. Figure 1-3 shows the combinations that result in a typed variable. The most verbose style is to add a type annotation and assign the value. Although this is the style shown in many examples in this chapter, in practice this is the one you will use the least. The second variation shows a type annotation with no value assignment; the type annotation here is required because TypeScript cannot infer the type when there is no value present. The final example is just like plain JavaScript; a variable is declared and initialized on the same line. In TypeScript the type of the variable is inferred from the value assigned.

9781430267911_Fig01-03.jpg

Figure 1-3. Typed variable combinations

To demonstrate type annotations in code, Listing 1-4 shows an example of a variable that has an explicit type annotation that marks the variable as a string. Primitive types are the simplest form of type annotation, but you are not restricted to such simple types.

Listing 1-4. Explicit type annotation

var name: string = 'Steve';

The type used to specify an annotation can be a primitive type, an array type, a function signature, or any complex structure you want to represent including the names of classes and interfaces you create. If you want to opt out of static type checking, you can use the special any type, which marks a variable’s type as dynamic. No checks are made on dynamic types. Listing 1-5 shows a range of type annotations that cover some of these different scenarios.

Listing 1-5. Type annotations

// primitive type annotation
var name: string = 'Steve';
var heightInCentimeters: number = 182.88;
var isActive: boolean = true;

// array type annotation
var names: string[] = ['James', 'Nick', 'Rebecca', 'Lily'];

// function annotation with parameter type annotation and return type annotation
var sayHello: (name: string) => string;

// implementation of sayHello function
sayHello = function (name: string) {
    return 'Hello ' + name;
};

// object type annotation
var person: { name: string; heightInCentimeters: number; };

// Implementation of a person object
person = {
    name: 'Mark',
    heightInCentimeters: 183
};

Image Note  Although many languages specify the type before the identifier, the placement of type annotations in TypeScript after the identifier helps to reinforce that the type annotation is optional. This style of type annotation is also inspired by type theory.

If a type annotation becomes too complex, you can create an interface to represent the type to simplify annotations. Listing 1-6 demonstrates how to simplify the type annotation for the person object, which was shown at the end of the previous example in Listing 1-5. This technique is especially useful if you intend to reuse the type as it provides a re-usable definition. Interfaces are not limited to describing object types; they are flexible enough to describe any structure you are likely to encounter. Interfaces are discussed in more detail later in this chapter.

Listing 1-6. Using an interface to simplify type annotations

interface Person {
    name: string;
    heightInCentimeters: number;
}

var person: Person = {
    name: 'Mark',
    heightInCentimeters: 183
}

Primitive Types

Although the primitive types seem limited in TypeScript, they directly represent the underlying JavaScript types and follow the standards set for those types. String variables can contain a sequence of UTF-16 code units. A Boolean type can be assigned only the true or false literals. Number variables can contain a double-precision 64-bit floating point value. There are no special types to represent integers or other specific variations on a number as it wouldn’t be practical to perform static analysis to ensure all possible values assigned are valid.

The any type is exclusive to TypeScript and denotes a dynamic type. This type is used whenever TypeScript is unable to infer a type, or when you explicitly want to make a type dynamic. Using the any type is equivalent to opting out of type checking for the life of the variable.

Image Caution  Before version 0.9 of TypeScript, the Boolean type was described using the bool keyword. There was a breaking change in the 0.9 TypeScript language specifications, which changed the keyword to boolean.

The type system also contains three types that are not intended to be used as type annotations but instead refer to the absence of values.

  • The undefined type is the value of a variable that has not been assigned a value.
  • The null type can be used to represent an intentional absence of an object value. For example, if you had a method that searched an array of objects to find a match, it could return null to indicate that no match was found.
  • The void type is used only on function return types to represent functions that do not return a value or as a type argument for a generic class or function.

Arrays

TypeScript arrays have precise typing for their contents. To specify an array type, you simply add square brackets after the type name. This works for all types whether they are primitive or custom types. When you add an item to the array its type will be checked to ensure it is compatible. When you access elements in the array, you will get quality autocompletion because the type of each item is known. Listing 1-7 demonstrates each of these type checks.

Listing 1-7. Typed arrays

interface Monument {
    name: string;
    heightInMeters: number;
}

// The array is typed using the Monument interface
var monuments: Monument[] = [];

// Each item added to the array is checked for type compatibility
monuments.push({
    name: 'Statue of Liberty',
    heightInMeters: 46,
    location: 'USA'
});

monuments.push({
    name: 'Peter the Great',
    heightInMeters: 96
});

monuments.push({
    name: 'Angel of the North',
    heightInMeters: 20
});

function compareMonumentHeights(a: Monument, b: Monument) {
    if (a.heightInMeters > b.heightInMeters) {
        return -1;
    }
    if (a.heightInMeters < b.heightInMeters) {
        return 1;
    }
    return 0;
}

// The array.sort method expects a comparer that accepts two Monuments
var monumentsOrderedByHeight = monuments.sort(compareMonumentHeights);

// Get the first element from the array, which is the tallest
var tallestMonument = monumentsOrderedByHeight[0];

console.log(tallestMonument.name); // Peter the Great

There are some interesting observations to be made in Listing 1-7. When the monuments variable is declared, the type annotation for an array of Monument objects can either be the shorthand: Monument[] or the longhand: Array<Monument>—there is no difference in meaning between these two styles. Therefore, you should opt for whichever you feel is more readable. Note that the array is instantiated after the equals sign using the empty array literal ([]). You can also instantiate it with values, by adding them within the brackets, separated by commas.

The objects being added to the array using monuments.push(...) are not explicitly Monument objects. This is allowed because they are compatible with the Monument interface. This is even the case for the Statue of Liberty object, which has a location property that isn’t part of the Monument interface. This is an example of structural typing, which is explained in more detail in Chapter 2.

The array is sorted using monuments.sort(...), which takes in a function to compare values. When the comparison is numeric, the comparer function can simply return a - b, in other cases you can write custom code to perform the comparison and return a positive or negative number to be used for sorting (or a zero if the values are the same).

The elements in an array are accessed using an index. The index is zero based, so the first element in the monumentsOrderedByHeight array is monumentsOrderedByHeight[0]. When an element is accessed from the array, autocompletion is supplied for the name and heightInMeters properties. The location property that appears on the Statue of Liberty object is not supplied in the autocompletion list as it isn’t part of the Monument interface.

To find out more about using arrays and loops, refer to Appendix 1.

Enumerations

Enumerations represent a collection of named elements that you can use to avoid littering your program with hard-coded values. By default, enumerations are zero based although you can change this by specifying the first value, in which case numbers will increment from the specified value. You can opt to specify values for all identifiers if you wish to. In Listing 1-8 the VehicleType enumeration can be used to describe vehicle types using well-named identifiers throughout your program. The value passed when an identifier name is specified is the number that represents the identifier, for example in Listing 1-8 the use of the VehicleType.Lorry identifier results in the number 5 being stored in the type variable. It is also possible to get the identifier name from the enumeration by treating the enumeration like an array.

Listing 1-8. Enumerations

enum VehicleType {
    PedalCycle,
    MotorCycle,
    Car,
    Van,
    Bus,
    Lorry
}

var type = VehicleType.Lorry;

var typeName = VehicleType[type]; // 'Lorry'

In TypeScript enumerations are open ended. This means all declarations with the same name inside a common root will contribute toward a single type. When defining an enumeration across multiple blocks, subsequent blocks after the first declaration must specify the numeric value to be used to continue the sequence, as shown in Listing 1-9. This is a useful technique for extending code from third parties, in ambient declarations and from the standard library.

Listing 1-9. Enumeration split across multiple blocks

enum BoxSize {
    Small,
    Medium
}

//...

enum BoxSize {
    Large = 2,
    XLarge,
    XXLarge
}

Image Note  The term common root comes from graph theory. In TypeScript this term relates to a particular location in the tree of modules within your program. Whenever declarations are considered for merging, they must have the same fully qualified name, which means the same name at the same level in the tree.

Bit Flags

You can use an enumeration to define bit flags. Bit flags allow a series of items to be selected or deselected by switching individual bits in a sequence on and off. To ensure that each value in an enumeration relates to a single bit, the numbering must follow the binary sequence whereby each value is a power of two, e.g.,

1, 2, 4, 8, 16, 32, 64, 128, 256, 512, 1,024, 2,048, 4,096, and so on

Listing 1-10 shows an example of using an enumeration for bit flags. By default when you create a variable to store the state, all items are switched off. To switch on an option, it can simply be assigned to the variable. To switch on multiple items, items can be combined with the bitwise OR operator (|). Items remain switched on if you happen to include them multiple times using the bitwise OR operator. Bitwise Flags are explained in detail in Appendix 3.

Listing 1-10. Flags

enum DiscFlags {
        None = 0,
        Drive = 1,
        Influence = 2,
        Steadiness = 4,
        Conscientiousness = 8
}

// Using flags
var personality = DiscFlags.Drive | DiscFlags.Conscientiousness;

// Testing flags

// true
var hasD = (personality & DiscFlags.Drive) == DiscFlags.Drive;

// true
var hasI = (personality & DiscFlags.Influence) == DiscFlags.Influence;

// false
var hasS = (personality & DiscFlags.Steadiness) == DiscFlags.Steadiness;

// false
var hasC = (personality & DiscFlags.Conscientiousness) == DiscFlags.Conscientiousness;

Type Assertions

In cases in which TypeScript determines that an assignment is invalid, but you know that you are dealing with a special case, you can override the type using a type assertion. When you use a type assertion, you are guaranteeing that the assignment is valid in a scenario where the type system has found it not to be—so you need to be sure that you are right, otherwise your program may not work correctly. The type assertion precedes a statement, as shown in Listing 1-11. The avenueRoad variable is declared as a House, so a subsequent assignment to a variable declared as Mansion would fail. Because we know that the variable is compatible with the Mansion interface (it has all three properties required to satisfy the interface), the type assertion <Mansion> confirms this to the compiler.

Listing 1-11. Type assertions

interface House {
    bedrooms: number;
    bathrooms: number;
}

interface Mansion {
    bedrooms: number;
    bathrooms: number;
    butlers: number;
}

var avenueRoad: House = {
    bedrooms: 11,
    bathrooms: 10,
    butlers: 1
};

// Errors: Cannot convert House to Mansion
var mansion: Mansion = avenueRoad;

// Works
var mansion: Mansion = <Mansion>avenueRoad;

Although a type assertion overrides the type as far as the compiler is concerned, there are still checks performed when you assert a type. It is possible to force a type assertion, as shown in Listing 1-12, by adding an additional <any> type assertion between the actual type you want to use and the identifier of the variable.

Listing 1-12. Forced type assertions

var name: string = 'Avenue Road';

// Error: Cannot convert 'string' to 'number'
var bedrooms: number = <number> name;

// Works
var bedrooms: number = <number> <any> name;

Operators

All of the standard JavaScript operators are available within your TypeScript program. The JavaScript operators are described in Appendix 1. This section describes operators that have special significance within TypeScript because of type restrictions or because they affect types.

Increment and Decrement

The increment (++) and decrement (--) operators can only be applied to variables of type any, number, or enum. This is mainly used to increase index variables in a loop or to update counting variables in your program, as shown in Listing 1-13. In these cases you will typically be working with a number type. The operator works on variables with the any type, as no type checking is performed on these variables.

Listing 1-13. Increment and decrement

var counter = 0;

do {
    ++counter;
} while (counter < 10);

alert(counter); // 10

When incrementing or decrementing an enumeration, the number representation is updated. Listing 1-14 shows how incrementing the size variable results in the next element in the enumeration and decrementing the size variable results in the previous element in the enumeration. Beware when you use this method as you can increase and decrease the value beyond the bounds of the enumeration.

Listing 1-14. Increment and decrement of enumerations

enum Size {
    S,
    M,
    L,
    XL
}

var size = Size.S;
++size;
console.log(Size[size]); // M

var size = Size.XL;
--size;
console.log(Size[size]); // L

var size = Size.XL;
++size;
console.log(Size[size]); // undefined

Binary Operators

The operators in the following list are designed to work with two numbers. In TypeScript, it is valid to use the operators with variables of type number or any. Where you are using a variable with the any type, you should ensure it contains a number. The result of an operation in this list is always a number.

Binary operators:   - *  /  %  <<  >>  >>>  &  ^  |

The plus (+) operator is absent from this list because it is a special case; a mathematical addition operator as well as a concatenation operator. Whether the addition or concatenation is chosen depends on the type of the variables on either side of the operator. As Listing 1-15 shows, this is a common problem in JavaScript programs in which an intended addition results in the concatenation of the two values, resulting in an unexpected value. This will be caught in a TypeScript program if you try to assign a string to a variable of the number type, or try to return a string for a function that is annotated to return a number.

Listing 1-15. Binary plus operator

// 6: number
var num = 5 + 1;

// '51': string
var str = 5 + '1';

The rules for determining the type resulting from a plus operation are

  • If the type of either of the arguments is a string, the result is always a string.
  • If the type of both arguments is either number or enum, the result is a number.
  • If the type of either of the arguments is any, and the other argument is not a string, the result is any.
  • In any other case, the operator is not allowed.

When the plus operator is used with only a single argument, it acts as a shorthand conversion to a number. This unary use of the plus operator is illustrated in Listing 1-16. The unary minus operator also converts the type to number and changes its sign.

Listing 1-16. Unary plus and minus operators

var str: string = '5';

// 5: number
var num = +str;

// -5: number
var negative = -str;

Bitwise Operators

Bitwise operators in TypeScript accept values of all types. The operator treats each value in the expression as a sequence of 32 bits and returns a number. Bitwise operators are useful for working with Flags, as discussed in the earlier section on Enumerations.

The full list of bitwise operators is shown in Table 1-1.

Table 1-1. Bitwise Operators

Operator

Name

Description

&

AND

Returns a result with a 1 in each position that both inputs have a 1.

|

OR

Returns a result with a 1 in each position where either input has a 1.

^

XOR

Returns a result with a 1 in each position where exactly one input has a 1.

<<

Left Shift

Bits in the left hand argument are moved to the left by the number of bits specified in the right hand argument. Bits moved off the left side are discarded and zeroes are added on the right side.

>>

Right Shift

Bits in the left hand argument are moved to the right by the number of bits specified in the right hand argument. Bits moved off the right side are discarded and digits matching the left most bit are added on the left side.

>>>

Zero-fill Right Shift

Bits in the left hand argument are moved to the right by the number of bits specified in the right hand argument. Bits moved off the right side are discarded and zeroes are added on the left side.

~

NOT

Accepts a single argument and inverts each bit.

Logical Operators

Logical operators are usually used to test Boolean variables or to convert an expression into a Boolean value. This section explains how logical operators are used in TypeScript for this purpose, and also how logical AND and logical OR operators can be used outside of the context of Boolean types.

NOT Operator

The common use of the NOT (!) operator is to invert a Boolean value; for example, if (!isValid) conditionally runs code if the isValid variable is false. Using the operator in this way does not affect the type system.

The NOT operator can be used in TypeScript in ways that affect types. In the same way the unary plus operator can be used as a shorthand method for converting a variable of any type to a number, the NOT operator can convert any variable to a Boolean type. This can be done without inverting the truth of the variable by using a sequence of two unary NOT operators (!!). Both of these are illustrated in Listing 1-17. Traditionally, a single ! is used to invert a statement to reduce nesting in your code, whereas the double !! converts a type to a Boolean.

Listing 1-17. NOT operator

var truthyString = 'Truthy string';
var falseyString: string;

// False, it checks the string but inverts the truth
var invertedTest = ! truthyString;

// True, the string is not undefined or empty
var truthyTest = !! truthyString;

// False, the string is empty
var falseyTest = !! falseyString;

When converting to a Boolean using this technique, the JavaScript style type juggling rules apply. For this reason it is worth familiarizing yourself with the concepts of “truthy” and “falsey” that apply to this operation. The term falsey applies to certain values that are equivalent to false when used in a logical operation. Everything else is “truthy” and is equivalent to true. The following values are “falsey” and are evaluated as false

  • undefined
  • null
  • false: boolean
  • '': string (empty string)
  • 0: number
  • NaN (the JavaScript Not a Number value)

All other values are evaluated as true. Surprising examples of this include:

  • '0': string
  • 'False': string

This style of checking differs from other languages, but allows a rather powerful shorthand test of a variable as shown in Listing 1-18. Given that a variable can be undefined or null, and you probably don’t want to check for both, this is a useful feature. If you want to perform a type-safe check with no juggling, you can use the three-character operators === or !==; for example, if (myProperty === false) tests that the type on both sides of the comparison are the same and their values are the same.

Listing 1-18. Shorthand Boolean test

var myProperty;

if (myProperty) {
    // Reaching this location means that...
    // myProperty is not null
    // myProperty is not undefined
    // myProperty is not boolean false
    // myProperty is not an empty string
    // myProperty is not the number 0
    // myProperty is not NaN
}

AND Operator

The common use of the logical AND operator (&&) is to assert that both sides of a logical expression are true, for example, if (isValid && isRequired). If the left hand side of the expression is false (or is falsey, meaning it can be converted to false), the evaluation ends. Otherwise, the right hand side of the expression is evaluated.

The AND operator can also be used outside of a logical context because the right hand side of the expression is only evaluated if the left hand side is truthy. In Listing 1-19, the console.log function is only called if the console object is defined. In the second example the player2 variable is only set if there is already a player1 value. Where the result of the expression is assigned to a variable, the variable will always have the type of the right hand expression.

Listing 1-19. AND operator

// longhand
if (console) {
    console.log('Console Available'),
}

// shorthand
console && console.log('Console Available'),

var player1 = 'Martin';

// player2 is only defined if player1 is defined
var player2 = player1 && 'Dan';

// 'Dan'
alert(player2);

OR Operator

The common use of the logical OR (||) operator is to test that one of two sides to an expression are true. The left hand side is evaluated first and the evaluation ends if the left hand side is true. If the left hand side is not true, the right hand side of the expression is evaluated.

The less common use of the OR operator is to coalesce two values, substituting a value on the left with one on the right in cases where the left hand value is falsey. Listing 1-20 illustrates this usage. The result has the best common type between the two types in the expression. Best common types are explained in more detail in Chapter 2.

Listing 1-20. OR operator

// Empty strings are falsey
var errorMessages = '';

// result is 'Saved OK'
var result = errorMessages || 'Saved OK';


// Filled strings are truthy
errorMessages = 'Error Detected';

// result is 'Error Detected'
result = errorMessages || 'Saved OK';


var undefinedLogger;

// if the logger isn't initialized, substitute it for the result of the right-hand expression
var logger = undefinedLogger || { log: function (msg: string) { alert(msg); } };

// alerts 'Message'
logger.log('Message'),

Short-Circuit Evaluation

Both the logical AND operator and the logical OR operator benefit from short-circuit evaluation. This means that as soon as the statement can be logically answered, evaluation stops. Whilst this saves the processing of the second statement, the real benefit is that it means you can ensure a value is defined before you use it.

In Listing 1-21, the if-statement would fail in a language that didn’t support short-circuit evaluation because a property is being accessed on the caravan variable, which is undefined. Because an undefined variable is falsey, only the left hand of the expression needs to be evaluated to know that the whole expression is false, so the caravan.rooms property is never accessed.

Listing 1-21. Short-circuit evaluation

interface Caravan {
    rooms: number;
}

var caravan: Caravan;

if (caravan && caravan.rooms > 5) {
    //...
}

Conditional Operator

When you write an if-else statement that results in different values being assigned to the same variable (as shown in Listing 1-22), you can shorten your code using a conditional operator. The conditional operator is a shorthand way to assign one of two values based on a logical test, as illustrated in Listing 1-23. When a conditional operator is used in TypeScript, the result has the best common type between the two possible values. Best common types are described in Chapter 2.

Listing 1-22. The If-statement

var isValid = true;

// Long-hand equivalent
if (isValid) {
    message = 'Okay';
} else {
    message = 'Failed';
}

Listing 1-23. Conditional operator

var isValid = true;

// Conditional operator
var message = isValid ? 'Okay' : 'Failed';

Type Operators

There are a collection of operators available that can assist you when working with objects in JavaScript. Operators such as typeof, instanceof, in, and delete are particularly relevant to working with classes, you will find more information on these operators in the section on classes later in this chapter.

Functions

Now you have an understanding of the detailed minutia of types, you are ready to apply that knowledge to a subject that is right at the heart of a TypeScript program: functions. Although there are some interesting code organization options using classes and modules, functions are the building blocks of readable, maintainable, and re-usable code.

In TypeScript you are likely to find that most functions are actually written as methods that belong to a class. It makes sense to use modules and classes to organize your code into logical units. Whether you choose to use these structural elements, functions are improved by a number of TypeScript language features.

With variables, there is just a single location for a type annotation, which is directly after the identifier. With functions there are a number of places that can be annotated with type information. In Listing 1-24 you will see that each parameter can be given a type annotation. In the example in Listing 1-24, the getAverage function accepts three parameters and each one can have a different type. When the function is called, the type of each argument passed to the function is checked. The types are also known within the function, which allows sensible autocompletion suggestions and type checking inside the function body.

There is an additional type annotation outside of the parentheses that indicates the return type. In Listing 1-24 the function returns a string. Each return statement is checked against this annotation to ensure the return value is compatible with the return type. You can use the void type to indicate that the function does not return a value. This will prevent code inside the function from returning a value and stop calling code from assigning the result of the function to a variable.

Listing 1-24. Function type annotations

function getAverage(a: number, b: number, c: number): string {
    var total = a + b + c;
    var average = total / 3;
    return 'The average is ' + average;
}

var result = getAverage(4, 3, 8); // 'The average is 5'

Although it is possible to specify all of the types used in a function explicitly, you can rely on type inference rather than explicitly writing annotations for everything in your program. This is explained in detail in Chapter 2. For functions it is worth leaving out the return type unless the function returns no value. If you don’t intend to return a value, an explicit void type will prevent a return value being added to a function at a later date that could break the design. In cases where a value is returned, TypeScript will check that all return statements are compatible with each other and issue the error “Could not find the best common type of types of all return statement expressions” if they are not.

Optional Parameters

In JavaScript, it is possible to call a function without supplying any arguments, even where the function specifies parameters. It is even possible in JavaScript to pass more arguments than the function requires. In TypeScript, the compiler checks each call and warns you if the arguments fail to match the required parameters in number or type.

Because arguments are thoroughly checked, you need to annotate optional parameters to inform the compiler that it is acceptable for an argument to be omitted by calling code. To make a parameter optional, suffix the identifier with a question mark, as shown in Listing 1-25, which is an updated version of the getAverage function, which accepts either two or three arguments.

Listing 1-25. Optional paramters

function getAverage(a: number, b: number, c?: number): string {
    var total = a;
    var count = 1;

    total += b;
    count++;

    if (typeof c !== 'undefined') {
        total += c;
        count++;
    }

    var average = total / count;
    return 'The average is ' + average;
}

var result = getAverage(4, 6); // 'The average is 5'

Optional parameters must be located after any required parameters in the parameter list. For example, the second parameter cannot be optional if the third parameter is required.

When you use an optional parameter you must check the value to see if it has been initialized. The typeof check is the common pattern for this check. If you used the shorthand check if (b), you would find that empty string and numeric zeroes would be treated as if the variable was undefined. The longer expression if (typeof b === 'undefined') avoids this by thoroughly checking the type and value.

Default Parameters

Default parameters are complementary to optional parameters. Wherever you consider using an optional parameter you should also consider the use of a default parameter as an alternative design. When you specify a default parameter, it allows the argument to be omitted by calling code and in cases where the argument is not passed the default value will be used instead.

To supply a default value for a parameter, assign a value in the function declaration as shown in Listing 1-26.

Listing 1-26. Default parameters

function concatenate(items: string[], separator = ',', beginAt = 0, endAt = items.length) {
        var result = '';

        for (var i = beginAt; i < endAt; i++) {
                result += items[i];
                if (i < (endAt - 1)) {
                        result += separator;
                }
        }

        return result;
}

var items = ['A', 'B', 'C'];

// 'A,B,C'
var result = concatenate(items);

// 'B-C'
var partialResult = concatenate(items, '-', 1);

The JavaScript code generated by default parameters includes a typeof check just as the one manually written for optional parameters in Listing 1-25. This means that the default parameters result in a check inside the function body that assigns the default value if no argument is passed. In the case of default parameters, though, these checks only appear in the output, which keeps the TypeScript code listing short and succinct. Because the checks are moved inside the function body, you can use a wide range of runtime values as default values—you aren’t restricted to compile-time constants as you are in other languages. The default value could be calculated (as is the case for parameter endAt in Listing 1-26), or refer to any variable that could be accessed from within the function body.

Rest Parameters

Rest parameters allow calling code to specify zero or more arguments of the specified type. For the arguments to be correctly passed, rest parameters must follow these rules

  • Only one rest parameter is allowed.
  • The rest parameter must appear last in the parameter list.
  • The type of a rest parameter must be an array type.

To declare a rest parameter, prefix the identifier with three periods and ensure that the type annotation is an array type, as shown in Listing 1-27.

Listing 1-27. Rest Parameters

function getAverage(...a: number[]): string {
    var total = 0;
    var count = 0;

    for (var i = 0; i < a.length; i++) {
        total += a[i];
        count++;
    }

    var average = total / count;
    return 'The average is ' + average;
}

var result = getAverage(2, 4, 6, 8, 10); // 'The average is 6'

Your function should expect to receive any number of arguments, including none. In your compiled JavaScript code, you will see that the compiler has added code to map the arguments list to your array variable within the method body.

Image Note  If you require that at least one argument is passed, you would need to add a required parameter before the rest parameter to enforce this minimum requirement. This would be the correct signature for the getAverage function in Listing 1-27 to avoid a potential divide-by-zero error.

Overloads

I have deliberately covered optional, default, and rest parameters before introducing function overloads; in most cases you can write a method using parameter language features and avoid writing an overload. Where this isn’t possible, you should consider writing separate, well-named functions that make their different intentions explicit. That isn’t to say that there are no valid uses for function overloads and if you have considered the other options and chosen to use overloads that is a perfectly reasonable selection.

In many languages, each overload has its own implementation but in TypeScript the overloads all decorate a single implementation, as highlighted in Listing 1-28. The actual signature of the function appears last and is hidden by the overloads. This final signature is called an implementation signature. The implementation signature must define parameters and a return value that are compatible with all preceding signatures. As this implies, the return types for each overload can be different and the parameter lists can differ not only in types, but also in number of arguments. If an overload specifies fewer parameters than the implementation signature, the implementation signature would have to make the extra parameters optional, default, or rest parameters.

Listing 1-28. Overloads

function getAverage(a: string, b: string, c: string): string;
function getAverage(a: number, b: number, c: number): string;
// implementation signature
function getAverage(a: any, b: any, c: any): string {
    var total = parseInt(a, 10) + parseInt(b, 10) + parseInt(c, 10);
    var average = total / 3;
    return 'The average is ' + average;
}

var result = getAverage(4, 3, 8); // 5

When you call a function that has overloads defined, the compiler constructs a list of signatures and attempts to determine the signature that matches the function call. If there are no matching signatures the call results in an error. If one or more signature matches, the earliest of the matching signatures (in the order they appear in the file) determines the return type.

Overloads introduce a burden to the function as types may need to be tested or converted, and they may cause multiple logical branches within the function. In cases where the types are compatible and no additional code needs to be written within the function, overloads allow a single function to be used in multiple cases.

Image Note  When you use overloads, the implementation signature cannot be called directly, so any calls must be compatible with one of the overloads.

Specialized Overload Signatures

Specialized overload signatures refer to the ability in TypeScript to create overloads based on string constants. Rather than the overloads being based on different parameters, they are based on the string value of an argument as shown in Listing 1-29. This allows a single implementation of a function to be re-used in many cases without requiring the calling code to convert the types.

Listing 1-29. Specialized overload signatures

class HandlerFactory {
    getHandler(type: 'Random'): RandomHandler;
    getHandler(type: 'Reversed'): ReversedHandler;
    getHandler(type: string): Handler; // non-specialized signature
    getHandler(type: string): Handler { // implementation signature
        switch (type) {
            case 'Random':
                return new RandomHandler();
            case 'Reversed':
                return new ReversedHandler();
            default:
                return new Handler();
        }
    }
}

There are some rules to follow when using specialized overload signatures

  • There must be at least one nonspecialized signature.
  • Each specialized signature must return a subtype of a nonspecialized signature.
  • The implementation signature must be compatible with all signatures.

The most common case for specialized signatures is that the nonspecialized signature returns a superclass, with each overload returning a more specialized subclass that inherits (or is structurally compatible with) the superclass. This is how the definition for the Document Object Model (DOM) method getElementsByTagName is declared in the TypeScript standard library, which means you get back an appropriately typed NodeList depending on the HTML tag name you pass to the function. An extract of this method signature is shown in Listing 1-30.

Listing 1-30. getElementsByTagName

// This example does not list all variations...
getElementsByTagName(name: "a"): NodeListOf<HTMLAnchorElement>;
getElementsByTagName(name: "blockquote"): NodeListOf<HTMLQuoteElement>;
getElementsByTagName(name: "body"): NodeListOf<HTMLBodyElement>;
getElementsByTagName(name: "button"): NodeListOf<HTMLButtonElement>;
getElementsByTagName(name: "form"): NodeListOf<HTMLFormElement>;
getElementsByTagName(name: "h1"): NodeListOf<HTMLHeadingElement>;
getElementsByTagName(name: string): NodeList; // Non-specialized signature
getElementsByTagName(name: string): NodeList { // implementation signature
    return document.getElementsByTagName(name);
}

When you write signatures that satisfy these rules, you may find that your implementation signature is identical to your nonspecialized signature. Remember that the implementation signature is hidden from calling code, so although it looks like duplication, it is necessary. This is illustrated in Listing 1-29, which shows how specialized subclasses are annotated as the return type where a specific value is passed in the type parameter.

This is an unusual technique, but is necessary for the purposes of defining the behavior you would expect from web browsers. The specialized overloads inspect the value being passed and select the overload based on that value, for example, if you pass a name argument with the value "blockquote", the second signature in Listing 1-31 will be matched and the return type is NodeListOf<HTMLQuoteElement>.

Listing 1-31. Arrow functions

var addNumbers = (a: number, b: number) => a + b;

var addNumbers = (a: number, b: number) => {
    return a + b;
}

var addNumbers = function (a: number, b: number) {
    return a + b;
}

Arrow Functions

TypeScript provides shorthand syntax for defining a function. The arrow function is inspired by proposed additions to the ECMAScript standard. Arrow functions allow you to leave out the function keyword and define your functions in an ultracompact way. All of the functions in Listing 1-31 result in identical JavaScript functions in the output when you target ECMAScript 3 or 5.

Image Note  The TypeScript compiler has options to target version 3 and version 5 of the ECMAScript specification and will support version 6 in the future. Version 4 of the ECMAScript specification was abandoned, so technically it doesn’t exist.

Each of the addNumbers functions in Listing 1-31 defines a function that accepts two numbers and returns the sum of those numbers. In the shortest example, although there is no return keyword, the compiler will return the result of the single expression. If you want to write multiple expressions, you will need to wrap the function in braces and use the return keyword.

Sometimes the single expression to be returned by an arrow function will be an object, for example; { firstName: 'Mark', lastName: 'Rendle' }. The braces around the object declaration confuse the TypeScript compiler, so you need to mark it as an expression by surrounding it with parentheses, as shown in Listing 1-32.

Listing 1-32. Wrapping an object in parentheses

var makeName = (f: string, l: string) => ({first: f, last: l});

You can also use an arrow syntax to preserve the lexical scope of the this keyword. This is particularly useful when working with callbacks or events as these are two situations where you are likely to lose the current scope. This is discussed in more detail in the section on classes later in this chapter, but it is also useful outside of classes as shown in Listing 1-33.

Listing 1-33. Preserving scope with arrow syntax

var ScopeLosingExample = {
    text: "Property from lexical scope",
    run: function () {
        setTimeout(function() {
            alert(this.text);
        }, 1000);
    }
};

// alerts undefined
ScopeLosingExample.run();

var ScopePreservingExample = {
    text: "Property from lexical scope",
    run: function () {
        setTimeout(() => {
            alert(this.text);
        }, 1000);
    }
};

// alerts "Property from lexical scope"
ScopePreservingExample.run();

The ScopeLosingExample object uses the standard syntax to create the function that is called when the timer expires. The scope of this is lost when the function is invoked by the timer, so the value of this.text is undefined, as we are no longer in the object context. In the ScopePreservingExample the only change is the use of the arrow syntax, which fixes the scope problem and allows the correct value to be obtained.

Behind the scenes, the TypeScript compiler creates a variable named _this just before the arrow function is defined and sets its value to the current value of this. It also substitutes any usages of this within the function with the newly introduced _this variable, so the statement now reads _this.text in the JavaScript output. The use of the _this variable inside the function creates a closure around the variable, which preserves its context along with the function. You can follow this pattern yourself, which is useful if you ever need both the original meaning of this as well as the functionally scoped meaning of this, such as when you are handling events.

Interfaces

TypeScript interfaces can be used for several purposes. As you would expect, an interface can be used as an abstract type that can be implemented by concrete classes, but they can also be used to define any structure in your TypeScript program. Interfaces are also the building blocks for defining operations that are available in third-party libraries and frameworks that are not written in TypeScript. There is more detail on writing ambient declarations to define external code in Chapter 8.

Interfaces are declared with the interface keyword and contain a series of annotations to describe the contract that they represent. The annotations can not only describe properties and functions as you might expect, but also constructors and indexers. When writing interfaces to describe classes you intend to implement in your program, you won’t need to define constructors or indexers. These features are included to help you describe external code with structures that may not be analogous to classes. This is discussed in Chapter 2.

Listing 1-34 demonstrates a set of interfaces to describe a vehicle, passengers, location, and destination. Properties and methods are declared using the familiar type annotations that have been used throughout this chapter. Constructors are declared using the new keyword.

Listing 1-34. Interfaces

interface Point {
        // Properties
        x: number;
        y: number;
}

interface Passenger {
        // Properties
        name: string;
}

interface Vehicle {
        // Constructor
        new() : Vehicle;

        // Properties
        currentLocation: Point;

        // Methods
        travelTo(point: Point);
        addPassenger(passenger: Passenger);
        removePassenger(passenger: Passenger);
}

Interfaces do not result in any compiled JavaScript code; this is due to type erasure, which is described in Chapter 2. Interfaces are used at design time to provide autocompletion and at compile time to provide type checking.

Just like enumerations, interfaces are open and all declarations with a common root are merged into a single interface. This means you must ensure that the combined interface is valid; you can’t declare the same property in multiple blocks of the same interface (you’ll receive a “duplicate identifier” error) and you can’t define the same method (although you can add overloads to an existing one).

Declaring an interface in several blocks is not a particularly valuable feature when you are writing your own program, but when it comes to extending built-in definitions or external code, this feature is priceless. For example, Figure 1-4 shows the available items on a NodeList: the item method and the length property. The built-in interface definition for NodeList is shown in Listing 1-35; the length property, item method, and the indexer are all included.

9781430267911_Fig01-04.jpg

Figure 1-4. The native NodeList

Listing 1-35. Built-in NodeList interface

interface NodeList {
    length: number;
    item(index: number): Node;
    [index: number]: Node;
}

If interfaces were closed, you would be limited to the contract defined in the standard library that ships with TypeScript, but in Listing 1-36, an additional interface block extends the built-in NodeList interface to add an onclick property that is not available natively. The implementation isn’t included in this example—it may be a new web standard that has yet to find its way into TypeScript’s standard library or a JavaScript library that adds the additional functionality. As far as the compiler is concerned, the interface that is defined in the standard library and the interface that is defined in your TypeScript file are one interface. You can find out more about extending existing objects in Chapter 4, and about specifically extending native browser functionality in Chapter 5.

Listing 1-36. Extending the NodeList interface

interface NodeList {
    onclick: (event: MouseEvent) => any;
}

var nodeList = document.getElementsByTagName('div'),

nodeList.onclick = function (event: MouseEvent) {
    alert('Clicked'),
};

It is worth reiterating that interfaces can not only be used to describe a contract you intend to implement in a class, but also that interfaces can be used to describe any structure you can conceive in your program whether they are functions, variables, objects, or combinations thereof. When a method accepts an options object as a parameter, which is common in JavaScript frameworks such as jQuery, an interface can be used to provide autocompletion for the complex object argument.

There is one other slightly obscure feature related to interfaces in TypeScript that is worth keeping in mind. An interface can inherit from a class in the same way a subclass can inherit from a superclass. When you do this, the interface inherits all of the members of the class, but without any implementation. Anything added to the class will also be added to the interface. You’ll find that this feature is particularly useful when used in conjunction with generics, which are explained later in this chapter.

Classes

Most of the preceding information on the TypeScript language has concerned various methods of annotating your code with type information. As you’ll read in Chapter 2, although it is important to understand all the various type annotations, TypeScript has powerful type inference that can do a lot of the work for you. The structural elements, on the other hand, will become familiar tools molded to the shape of your hands. Classes are the most fundamental structural element when it comes to organizing you program.

There are quite a few aspects to learn when working with classes, but if you have any previous experience with class-based object orientation many of the features will be recognizable, even if the details or syntax are new.

Constructors

All classes in TypeScript have a constructor, whether you specify one or not. If you leave out the constructor, the compiler will automatically add one. For a class that doesn’t inherit from another class, the automatic constructor will be parameterless and will initialize any class properties. Where the class extends another class, the automatic constructor will match the superclass signature and will pass arguments to the superclass before initializing any of its own properties.

Listing 1-37 shows two classes that have a manually written constructor. It is a slightly longer example than many of the other code listings in this chapter, but it is worth reading through it before each aspect is explained.

Listing 1-37. Constructors

class Song {
    constructor(private artist: string, private title: string) {

    }

    play() {
        console.log('Playing ' + this.title + ' by ' + this.artist);
    }
}

class Jukebox {
    constructor(private songs: Song[]) {
    }

    play() {
        var song = this.getRandomSong();
        song.play();
    }

    private getRandomSong() {
        var songCount = this.songs.length;
        var songIndex = Math.floor(Math.random() * songCount);

        return this.songs[songIndex];
    }
}

var songs = [
    new Song('Bushbaby', 'Megaphone'),
    new Song('Delays', 'One More Lie In'),
    new Song('Goober Gun', 'Stereo'),
    new Song('Sohnee', 'Shatter'),
    new Song('Get Amped', 'Celebrity')
];

var jukebox = new Jukebox(songs);

jukebox.play();

One of the first things that may strike you about the example is that the constructor parameters are not mapped to member variables. If you prefix a constructor parameter with an access modifier, such as private, it will automatically be mapped for you. You can refer to these constructor parameters as if they were declared as properties on the class, for example this.title, can be used anywhere within the Song class to obtain the song title on that instance. Listing 1-38 shows equivalent code where the parameters are manually mapped, but this is to illustrate the point that this creates a lot of redundant code, and you should avoid this approach.

Listing 1-38. Manually mapped constructor parameters

class Song {

    private artist: string;
    private title: string;

    constructor(artist: string, title: string) {
        this.artist = artist;
        this.title = title;
    }

    play() {
        console.log('Playing ' + this.title + ' by ' + this.artist);
    }
}

Access Modifiers

Access modifiers can be used to change the visibility of properties and methods within a class. By default, properties and methods are public—so you don’t need to prefix properties and methods with the public keyword. You do need to prefix constructor parameters with the public keyword if you want them to be mapped to public properties automatically.

To hide a property or method, you prefix it with the private keyword. This restricts the visibility to within the class only, the member won’t appear in autocompletion lists outside of the class and any external access will result in a compiler error. When you mark a class member as private, it can’t even be seen by subclasses. If you need to access a property or method from a subclass, it must be made public. When you use the private access modifier, the TypeScript compiler will enforce the privacy of the member, but at runtime there will be no enforcement of the visibility because it would require an additional closure in every class with private members.

There are plans to introduce a protected keyword, which will make a class member available within the class and also the subclasses—but this feature is currently under consideration for release after TypeScript version 1.0. You can track this feature on the TypeScript Codeplex project: http://typescript.codeplex.com/workitem/125

Properties and Methods

Instance properties are typically declared before the constructor in a TypeScript class. A property definition consists of three parts; an optional access modifier, the identifier, and a type annotation. For example: public name: string; You can also initialize the property with a value: public name: string = 'Jane'; When your program is compiled, the property initializers are moved into the constructor. Instance properties can be accessed from within the class using the this keyword. If the property is public it can be accessed using the instance name.

You can also add static properties to your class, which are defined in the same way as instance properties, but with the static keyword between the access modifier (if one is specified) and the identifier. Static properties are accessed using the class name as shown in Listing 1-39, where the static maxSongCount property is accessed using Playlist.maxSongCount—even within a method on the class; this is because the property is not defined on each instance.

Listing 1-39. Properties and methods

class Playlist {

    private songs: Song[] = [];

    static maxSongCount: number = 30;

    constructor(public name: string) {
    }

    addSong(song: Song) {
        if (this.songs.length >=Playlist.maxSongCount) {
            throw new Error('Playlist is full'),
        }

        this.songs.push(song);
    }
}

// Creating a new instance
var playlist = new Playlist('My Playlist'),

// Accessing a public instance property
var name = playlist.name;

// Calling a public instance method
playlist.addSong(new Song('Therapy?', 'Crooked Timber'));

// Accessing a public static property
var maxSongs = Playlist.maxSongCount;

Listing 1-39 also illustrates a typical method definition. Methods are defined a lot like functions, but they leave out the function keyword. You can annotate a method with all of the parameters and return value type annotations that were discussed earlier in the section on functions. You can prefix the method name with an access modifier to control its visibility, which is public by default. Just as with instance properties, methods can be accessed from within the class using the this keyword and if they are public they can be accessed outside of the class using the instance name.

You can create static methods by prefixing the method name with the static keyword. Static members can be called even when no instance of the class has been created and only a single instance of each static member exists in your program. All static members are accessed via the class name and not an instance name and static members have no access to nonstatic properties or methods.

TypeScript supports property getters and setters, as long as you are targeting ECMAScript 5 or above. The syntax for these is identical to method signatures as described in the following, except they are prefixed by either the get or set keyword. As shown in Listing 1-40, property getters and setters allow you to wrap property access with a method while preserving the appearance of a simple property to the calling code.

Listing 1-40. Property getters and setters

interface StockItem {
        description: string;
        asin: string;
}

class WarehouseLocation {
        private _stockItem;

        constructor(public aisle: number, public slot: string) {

        }

        get stockItem() {
                return this._stockItem;
        }

        set stockItem(item: StockItem) {
                this._stockItem = item;
        }
}

var figure = { asin: 'B001TEQ2PI', description: 'Figure' };

var warehouseSlot = new WarehouseLocation(15, 'A6'),

warehouseSlot.stockItem = figure;

Class Heritage

There are two types of class heritage in TypeScript. A class can implement an interface using the implements keyword and a class can inherit from another class using the extends keyword.

When you implement an interface, the implements declaration is entirely optional due to the structural types in TypeScript. If you do specify the interface using the implements keyword, your class will be checked to ensure that it complies with the contract promised by the interface. Listing 1-41 shows how the Song class implements the Audio interface. The play method must be implemented in the Song class, and its signature must be compatible with the Audio interface declaration. A class can implement multiple interfaces, with each interface being separated by a comma, for example: implements Audio, Video.

Listing 1-41. Class heritage

interface Audio {
    play(): any;
}

class Song implements Audio {
    constructor(private artist: string, private title: string) {
    }

    play() : void {
        console.log('Playing ' + this.title + ' by ' + this.artist);
    }

    static Comparer(a: Song, b: Song) {
        if (a.title === b.title) {
            return 0;
        }

        return a.title > b.title ? 1 : -1;
    }
}

class Playlist {
    constructor(public songs: Audio[]) {
    }

    play() {
        var song = this.songs.pop();
        song.play();
    }

    sort() {
        this.songs.sort(Song.Comparer);
    }
}

class RepeatingPlaylist extends Playlist {

    private songIndex = 0;

    constructor(songs: Song[]) {
        super(songs);
    }

    play() {
        this.songs[this.songIndex].play;

        this.songIndex++;

        if (this.songIndex >=this.songs.length) {
            this.songIndex = 0;
        }
    }
}

Image Note  A method on a class can have fewer parameters than the interface specifies. This allows a class to ignore arguments that it doesn’t require to execute the method. Any parameters that are specified must match the parameters in the interface.

You inherit from a class using the extends keyword, as shown in Listing 1-41. An extends clause makes your class a derived class, and it will gain all of the properties and methods of the base class from which it inherits. You can override a public member of the base class by adding a member of the same name and kind as the base class member. The RepeatingPlaylist inherits from the Playlist class and uses the songs property from the base class using this.songs, but overrides the play method with a specialized implementation that plays the next song in a repeating loop.

The constructor on the RepeatingPlaylist class shown in Listing 1-41 could be omitted because the automatic constructor that would be generated would match it exactly.

If the subclass accepts additional arguments there are a couple of rules you need to follow. The super call to the base class must be the first statement in the subclass constructor and you cannot specify an access modifier for a parameter on the subclass if it has an access modifier on the base class.

There are some rules that must be followed for inheritance

  • A class can only inherit from a single superclass.
  • A class cannot inherit from itself, either directly or via a chain of inheritance.

It is possible to create a class that inherits from another class and also implements multiple interfaces. In this case, the class must be a subtype of the base class as well as each interface.

Scope

If you call a class method from an event, or use it as a callback, the original context of the method can be lost, which results in problems using instance methods and instance properties. When the context is changed, the value of the this keyword is lost.

Listing 1-42 shows a typical example of lost context. If the registerClick method is called directly against the clickCounter instance, it works as expected. When the registerClick method is assigned to the onclick event, the context is lost and this.count is undefined in the new context.

Listing 1-42. Lost context

class ClickCounter {
    private count = 0;

    registerClick() {
        this.count++;
        alert(this.count);
    }
}

var clickCounter = new ClickCounter();

document.getElementById('target').onclick = clickCounter.registerClick;

There are several techniques that can be used to preserve the context to enable this to work, and you may choose to use different approaches in different scenarios.

Property and Arrow Function

You can replace the method with a property and initialize the property using an arrow function as shown in Listing 1-43. This is a reasonable technique if you know that the class will be consumed with events or callbacks, but it is less of an option if your class has no knowledge of when and where it may be called.

Listing 1-43. Preserving context with a property and an arrow function

class ClickCounter {
    private count = 0;

    registerClick = () => {
        this.count++;
        alert(this.count);
    }
}

Function Wrapping at Point of Call

If you want to leave your class untouched, you can wrap the call to the instance method in a function to create a closure that keeps the context alongside the function. This is demonstrated in Listing 1-44, which allows the use of this within the registerClick method without converting the method to a property.

Listing 1-44. Preserving context with a closure

document.getElementById('target').onclick = function () {
        clickCounter.registerClick();
};

ECMAScript 5 Bind Function

Another technique that leaves the original class untouched is to use JavaScript’s bind function, which is available in ECMAScript 5 and higher. The bind function permanently sets the context for the method. It can be used more generally to permanently replace the context, but in Listing 1-45 it is used to fix the context for the registerClick method to be the clickCounter instance.

Listing 1-45. Preserving context with bind

var clickHandler = clickCounter.registerClick.bind(clickCounter);

document.getElementById('target').onclick = clickHandler;

Choosing a Solution

There are several techniques you can use to ensure that your context is preserved when using a class instance method for callbacks and events. There are no fixed rules about which of these is the correct one to use; it depends on the specific use and your own design preferences.

If you are targeting slightly older browsers, the bind function may not be an option, but if older browsers aren’t an issue you may find it more graceful than a closure and it certainly makes your intent much clearer. The property and arrow-function technique is a neat trick, but perhaps allows too much knowledge of where the method is called to leak into your class.

One thing to take into account when designing your program will be the number of instances of each class being created at runtime. If you are creating hundreds or thousands of instances it is more efficient for the methods to be normal instance methods, not arrow functions assigned to properties. This is because normal instance methods are defined once and used by all instances. If you use a property and arrow function, it will be duplicated on every instance. This duplication can become a big overhead when a large number of instances are created.

Type Information

Obtaining types at runtime is a topic to be treated with some care. If your program tests types to control the flow of the program, you should be hearing alarm bells in your head. Checking types and branching off in different directions based on the type is a strong indicator that you have broken encapsulation. With this in mind, the following section describes how you can check types and obtain type names at runtime.

To test the type of a class instance, you use the instanceof operator. The operator is placed between the instance and the type you want to test, as shown in Listing 1-46. The test returns true if you have an instance of the specified class, or if the specified class appears anywhere in the inheritance chain. In all other cases, it returns false.

Listing 1-46. Using the instanceof operator

class Display {
    name: string = '';
}

class Television extends Display {

}

class HiFi {

}

var display = new Display();
var television = new Television();
var hiFi = new HiFi();

var isDisplay;

// true
isDisplay = display instanceof Display;

// true (inherits from Display)
isDisplay = television instanceof Display;

// false
isDisplay = hiFi instanceof Display;

You can also test the presence of specific properties using the in keyword. Expanding on the previous example from Listing 1-46, we can test for the presence of a name property as shown in Listing 1-47. The in operator will return true if the class has the property or if it inherits from a class that has the property.

Listing 1-47. The in property

var hasName;

// true
hasName = 'name' in display;

// false
hasName = 'name' in television;

// true
hasName = 'name' in hiFi;

It is important to note that due to the code generation in the TypeScript compiler, an uninitialized property will not be detected because unless the property has a value, it does not appear in the compiled JavaScript code. In Listing 1-48 the hasName property will be false because, although a name property is declared, the name property is never initialized. If the name property had been assigned a value, hasName would be true.

Listing 1-48. Uninitialized property

class Display {
    name: string;
}

var display = new Display();

// false
var hasName = 'name' in display;

Image Note  Don’t forget the quotes around the property name when using the in keyword as you will need to pass a string. Without the quotes, you would be testing the value of a variable, which may not even be defined.

If you want to obtain the type name at runtime, you may be tempted to use the typeof operator. Unfortunately, this will return the type name ‘object’ for all classes. This means you need to inspect the constructor of the instance to find the type name. This can be done using a regular expression as shown in Listing 1-49. The static Describer.getName method can be used to obtain the class name of any instance in your program.

Listing 1-49. Obtaining runtime types

class Describer {
    static getName(inputClass) {
        // RegEx to get the class name
        var funcNameRegex = /function (.{1,})(/;

        var results = (funcNameRegex).exec((<any> inputClass).constructor.toString());

        return (results && results.length > 1) ? results[1] : '';
    }
}

var tv = new Television();
var radio = new HiFi();

var tvType = Describer.getName(tv); // Television
var radioType = Describer.getName(radio); // HiFi

Modules

While classes are the most important structural element for code organization, because they lend themselves to common design patterns, modules are the most fundamental structural element when it comes to file organization and dynamically loading slices of your program. While modules give you some of the convenience of name spacing, their purpose is much more deliberate in TypeScript as they facilitate module loading.

There are two popular standards for loading modules at runtime. CommonJS is a framework class library for JavaScript and has a pattern for loading modules. There are many implementations of CommonJS designed both for servers and browsers. AMD (Asynchronous Module Definition) is simply an API for defining modules and is a popular style of loading modules in web browsers because of the asynchronous pattern.

Internal modules enclose their members within a function that limits their scope. The internal module name is added to the global scope and the exported members can be accessed via this globally scoped module identifier. External modules add nothing to the global scope. Instead, the members of an external module are made available via an alias when using CommonJS or limited to the scope of a callback function when using AMD.

There is further detail on module loading in web browsers using AMD in Chapter 5 and module loading on the server with CommonJS in Chapter 6.

Internal Modules

Internal modules can be used to group related features together. Each internal module is a singleton instance with all of the module’s contents enclosed within the module’s scope. By grouping variables, functions, objects, classes, and interfaces into modules, you can keep them out of the global scope and avoid naming collisions.

Modules are open ended and all declarations with the same name within a common root contribute toward a single module. This allows internal modules to be described in multiple files and will allow you to keep each file to a maintainable size.

Whereas class members are public by default, the content of a module body is hidden by default. To make an item available to code outside of the module body, you must prefix the item with the export keyword, as shown in Listing 1-50, where the Ship interface and Ferry class are the only items visible outside of the module.

Listing 1-50. Exporting from a module

module Shipping {

    // Available as Shipping.Ship
    export interface Ship {
        name: string;
        port: string;
        displacement: number;
    }

    // Available as Shipping.Ferry
    export class Ferry implements Ship {
        constructor(
            public name: string,
            public port: string,
            public displacement: number) {
        }
    }

    // Only available inside of the Shipping module
    var defaultDisplacement = 4000;

    class PrivateShip implements Ship {
        constructor(
            public name: string,
            public port: string,
            public displacement: number = defaultDisplacement) {
        }
    }

}

var ferry = new Shipping.Ferry('Assurance', 'London', 3220);

Although the export keyword makes module members available outside of the module, an import statement can be used within an internal module to provide an alias for another module or one of its members. In Listing 1-51 the Ship interface within the Shipping module is imported under the alias Ship. The alias can then be used throughout the Docking module as a short name, so wherever Ship appears within the module, it refers to Shipping.Ship. This is particularly useful if you have long module names or deep nesting in your program as it allows you to reduce the length of annotations.

Listing 1-51. Importing a module

module Docking {
    import Ship = Shipping.Ship;

    export class Dock {
        private dockedShips: Ship[] = [];

        arrival(ship: Ship) {
            this.dockedShips.push(ship);
        }
    }
}

var dock = new Docking.Dock();

Module names can contain periods. This allows you to create a naming hierarchy that acts like a namespace. For example, the following module names are all valid

  • module Transport.Maritime.Shipping  { //...
  • module Transport.Maritime.Docking   { //...
  • module Transport.Railways.Ticketing { //...

You do not need to have a Transport module for this to work (although you could if you wanted one); there will appear to be a Transport module that contains a Maritime module and a Railways module when autocompletion is shown. The naming allows features to be discovered logically using autocompletion. When you design your program structure, the discoverability of the features through autocompletion should be high on the list of factors that influence your naming.

Internal modules don’t benefit from automatic module loading as external modules do. You are free to implement your own method of loading scripts. On a scale of elegance from least graceful to most, you could use any of the following options:

  • Include each file in a script tag in your web page.
  • Compile the program into a single file and include it using a script tag.
  • Compile the program into a single file, minify it, and include it using a script tag.
  • Switch to external modules and use a module loader.

The final two options are actually both valid depending on the size of your program. There is a threshold you may reach where internal modules just aren’t a viable option any longer due to your program size. If your program becomes this large, loading smaller parts of the program as you need them may be the best bet and you are better off using external modules for this. For programs that can be combined and minified into a reasonably small file, internal modules can work well and result in fewer HTTP requests.

In some integrated development environments, the modules are recognized automatically and assumed to be present at runtime, which means in any given TypeScript file you will find autocompletion and type checking for all of the code in your program. In tools that don’t automatically look for dependent files, you can supply a hint using a reference comment. Listing 1-52 shows the reference comment required to make the Shipping module visible within the Docking module.

Listing 1-52. Reference comments

///<reference path="Shipping.ts" />

module Docking {
    import Ship = Shipping.Ship;

    export class Dock {
        private dockedShips: Ship[] = [];

        arrival(ship: Ship) {
            this.dockedShips.push(ship);
        }
    }
}

Image Tip  Remember that with some development tools; reference comments are optional.

If you are compiling your project into a single file using the TypeScript compiler, the reference comments serve an additional purpose of helping the compiler to order your output correctly, based on the dependencies. You can read more about using the TypeScript compiler to generate a combined single output file in Appendix 2.

External Modules

External modules are the key to scaling really big programs. Although you can combine and minify all of your JavaScript files to squash the size of a program, ultimately this will not scale forever. If you are working on a seriously large application, external modules and module loading are both indispensable tools.

External modules have a name that matches the path of the source file, without the file extension. Listing 1-53 recreates the Shipping module, but using external modules rather than internal modules. The export keyword is used to make members available outside of the module.

Listing 1-53. External modules: Shipping.ts

export interface Ship {
    name: string;
    port: string;
    displacement: number;
}

export class Ferry implements Ship {
    constructor(
        public name: string,
        public port: string,
        public displacement: number) {
    }
}

var defaultDisplacement = 4000;

class PrivateShip implements Ship {
    constructor(
        public name: string,
        public port: string,
        public displacement: number = defaultDisplacement) {
    }
}

Image Tip  When using external modules, you don’t need to wrap your code in a module block because the module is represented by the file.

To use an external module, you use an import statement along with a call to the require function as demonstrated in Listing 1-54. This is an important line of code in your program because it will be converted into code that loads your module at runtime. The require function accepts a string that represents the path to your source file, but without the file extension (i.e., no “.ts” on the end).

Listing 1-54. Importing external modules

import Shipping = require('./Shipping'),

export class Dock {
    private dockedShips: Shipping.Ship[] = [];

    arrival(ship: Shipping.Ship) {
        this.dockedShips.push(ship);
    }
}

To organize your program, you can use a folder structure that represents your namespaces. You will only ever state this full path inside of an import statement, so the length shouldn’t be a problem. All of the other code that references an external module will refer to it by the alias given in the import statement.

  • ./Transport/Maritime/Shipping
  • ./Transport/Maritime/Docking
  • ./Transport/Railways/Ticketing

Module Loading

Module loading comes in two flavors. CommonJS is the pattern of choice for NodeJS and a program using CommonJS will simply load a module each time the require function is called. Execution of the program continues once the module is loaded. AMD also loads a module each time the require function is called. Rather than pausing execution while the file loads, AMD passes the code as a callback. The callback is executed once the module has been loaded, allowing other code to be executed in the interim.

You can tell the TypeScript compiler which pattern you will use for module loading and it will generate the appropriate JavaScript output. You can read more detail about all of the compiler flags, including the module type in Appendix 2. A comparison of the two different output styles is shown in Listing 1-55.

Listing 1-55. JavaScript output for module loading

// CommonJS style
var dependency = require("./CommonJSDependency");
// your code

// AMD style
define(["require", "exports", 'AmdDependency'], function (require, exports, __dependency__) {
    var dependency = __dependency__;
    // your code} );

In the CommonJS example, your code is placed after the dependency. Execution pauses until the module is loaded. In the AMD example, your code is wrapped in a callback function that is executed when the module has loaded.

Export Assignments

Although the term module loading is used to describe loading source file dependencies, the result of the import is not restricted to modules. You can specify any module member to be used in place of the module using an export assignment. Only one export assignment can be used within a source file.

You can substitute a module with a variable, object, function, interface, or class. Listing 1-56 replaces the module with the greet function. When this module is imported, the alias will be a direct reference to the function, which shortens the calling code as you no longer have navigate via the module alias name to get to the greet function, you can simply execute the alias as the function.

Listing 1-56. Export assignments

function greet(name: string): void {
    console.log('Hello ' + name);
}

export = greet;

Listing 1-57 shows the statement that imports the greet module and gives it an alias of hello. Because of the export statement in the greet module, the hello alias is a direct reference to the greet function, not to the whole module. This would still be the case even if the module contained other blocks of code such as interfaces and classes as the export declaration replaces the module.

Listing 1-57. Calling the greet function

import hello = require('./scripts/greet'),

hello('Mark'), // instead of hello.greet('Mark'),

Module Merging

This section describes a feature that can be used with care to create special relationships between functions and modules and between classes and modules. Listing 1-58 demonstrates merging between a class and a module, but it works identically with a function and a module. In all cases, the module must appear after the class or function for the merge to work.

Listing 1-58. Class and module merging

// Class/Module Merging
class Car {

}

module Car {
    export class Engine {

    }

    export class GloveBox {

    }

}

var car = new Car();
var engine = new Car.Engine();
var gloveBox = new Car.GloveBox();

The main use of this would be to logically group subcomponents below a master component and have the master component as well as all subcomponents creatable using the new keyword. You could simply wrap everything inside of a Car module, but instantiating a new Car.Car is a grating expression and the module would fail to describe the relationship between the master and subcomponents.

Module merging can be an expressive programming style as long as it accurately describes the relationships in the real objects being represented in the code.

Generics

Generic programming allows algorithms to be written in way that allows the types to be specified later. This allows the types to be processed identically without sacrificing type safety or requiring separate instances of the algorithm to handle each type. It is possible to constrain the possible types used by the algorithm by specifying a type constraint.

In TypeScript it is possible to create generic functions, including generic methods, generic interfaces, and generic classes.

Generic Functions

To make a function generic, you add a type parameter enclosed in angle brackets (< >) immediately after the function name. The type parameter can then be used to annotate function parameters, the return type, or types used within the function (or any combination thereof). This is illustrated in Listing 1-59.

Listing 1-59. Generic functions

function reverse<T>(list: T[]) : T[] {
    var reversedList: T[] = [];

    for (var i = (list.length - 1); i >=0; i--) {
        reversedList.push(list[i]);
    }

    return reversedList;
}

var letters = ['a', 'b', 'c', 'd'];
var reversedLetters = reverse<string>(letters); // d, c, b, a

var numbers = [1, 2, 3, 4];
var reversedNumbers = reverse<number>(numbers); // 4, 3, 2, 1

When you call a generic function, you can specify the type argument by placing it in angle brackets after the function name. If the type can be inferred (e.g., by inspecting the types of the arguments passed to the function), the type argument becomes optional.

Image Tip  In both of the examples in Listing 1-59, the type arguments can be omitted because the compiler is able to infer the type based on the arguments passed to the function.

Generic Interfaces

To make a generic interface, the type parameters are placed directly after the interface name. Listing 1-60 shows a generic Repository interface that has two type parameters representing the type of a domain object and the type of an ID for that domain object. These type parameters can be used as annotations anywhere within the interface declaration.

Listing 1-60. Generic interfaces

class CustomerId {
    constructor(public customerIdValue: number) {
    }

    get value() {
        return this.customerIdValue;
    }
}

class Customer {
    constructor(public id: CustomerId, public name: string) {

    }
}

interface Repository<T, TId> {
    getById(id: TId): T;
    persist(model: T): TId;
}

class CustomerRepository implements Repository<Customer, CustomerId> {
    constructor(private customers: Customer[]) {

    }

    getById(id: CustomerId) {
        return this.customers[id.value];
    }

    persist(customer: Customer) {
        this.customers[customer.id.value] = customer;
        return customer.id;
    }
}

When the CustomerRepository class implements the generic interface, it supplies the concrete Customer and CustomerId types as type arguments. The body of the CustomerRepository class is checked to ensure that it implements the interface based on these types.

Generic Classes

If generic interfaces can save some duplication in your code, generic classes can save even more by supplying a single implementation to service many different type scenarios. The type parameters follow the class name and are surrounded by angle brackets. The type parameter can be used to annotate method parameters, properties, return types, and local variables within the class.

Listing 1-61 uses a generic class to provide a single implementation for all named ID types in a domain model. This allows all ids to be named without requiring individual implementations for each named type. This is a common pattern described by P. J. Plauger (Programming on Purpose, Prentice Hall, 1993) that prevents accidental substitution of values. This technique can be used in TypeScript, although there are some details to bear in mind when you implement the technique; these are discussed in Chapter 2.

Listing 1-61. Generic classes

class DomainId<T> {
    constructor(public id: T) {

    }

    get value(): T {
        return this.id;
    }
}

class OrderId extends DomainId<number> {
    constructor(public orderIdValue: number) {
        super(orderIdValue);
    }
}

class AccountId extends DomainId<string> {
    constructor(public accountIdValue: string) {
        super(accountIdValue);
    }
}

Type Constraints

A type constraint can be used to limit the types that a generic function, interface, or class can operate on. Listing 1-62 shows how an interface can be used to specify a contract that all types must satisfy to be used as a type argument. Type constraints are specified using the extends keyword, whether the constraint is an interface, a class, or a type annotation that describes the constraint.

Listing 1-62. Type constraints

interface HasName {
    name: string;
}

class Personalization {
    static greet<T extends HasName>(obj: T) {
        return 'Hello ' + obj.name;
    }
}

If a type argument is specified that does not satisfy the constraint, the compiler will issue an error. The constraint also allows the TypeScript language service to supply autocompletion suggestions for the generically typed members.

You can only specify a single class in a type constraint. Although you cannot specify multiple classes in a type constraint, you can create an interface that extends multiple classes and uses the interface as the constraint. Any types used with the constraint would then need to satisfy all of the class signatures that have been combined into the single interface.

TypeScript Futures

There are plans to add further features to the TypeScript language. In the short term, most language changes will be made to keep TypeScript in step with the ECMAScript 6 specification. The language will not be limited by ECMAScript developments though and the roadmap currently contains a range of features being considered for implementation, including

  • Async/Await
  • Mixins
  • The protected access modifier

Summary

This chapter has introduced all of the language features you need to write large scale application using TypeScript. You can revisit this chapter if you need to refer back to any of these features.You should have an understanding of type annotations, be able to use operators to perform shorthand type conversions, create routines inside of classes and modules to structure your program, and take advantage of generics to avoid near-duplicate implementations.

The next chapter provides a deep dive into the type system, which is especially important if your background is in nominally typed languages.

Key Points

  • All JavaScript is technically valid TypeScript.
  • Primitive types are closely linked to JavaScript primitive types.
  • Types are inferred in TypeScript, but you can supply annotations to make types explicit or deal with cases the compiler can’t handle.
  • Interfaces can be used to describe complicated structures, to make type annotations shorter.
  • All TypeScript arrays are generic.
  • You can use enumerations as bit flags.
  • There are special cases where type coercion applies, but in most cases type checking will generate errors for invalid use of types.
  • You can add optional, default, and rest parameters to functions and methods.
  • Arrow functions provide a short syntax for declaring functions, but can also be used to preserve the lexical scope.
  • Enumerations, interfaces, and modules are open, so multiple declarations that have the same name in the same common root will result in a single definition.
  • Classes bring structure to your TypeScript program and make it possible to use common design patterns.
  • Modules work as namespaces and external modules can help with module loading.
  • You can obtain type information at runtime, but this should be used responsibly.
..................Content has been hidden....................

You can't read the all page of ebook, please click here login for view all page.
Reset
18.223.170.63