I. TypeScript – Type Annotations
Similarly, we can declare an object with inline annotations for each of the properties of the object.
var employee : {
id: number;
name: string;
};
employee = {
id: 100,
name : "John"
}
If you try to assign a string value to id then the TypeScript compiler will give the following error.
error TS2322: Type '{ id: string; name: string; }' is not assignable to type
'{ id:number; name: string; }'.Types of property 'id' are incompatible.
Type 'string' is not assignable to type 'number'.
Type Inference in TypeScript
TypeScript is a typed language. However, it is not mandatory to specify the type of a variable. TypeScript infers types of variables when there is no explicit information available in the form of type annotations.Interfaces are used to define the structure of variables.
Types are inferred by TypeScript compiler when:
Variables are initialized Default values are set for parameters Function return types are determined
For example,
var a = "some text"
Here, since we are not explicitly defining a: string with a type annotation, TypeScript infers the type of the variable based on the value assigned to the variable. The value of a is a string and hence the type of a is inferred as string.
Type inference in complex objects
There may be scenarios where an object may be initialized with multiple types.
For example:
var arr = [ 10, null, 30, 40 ];
In the above example, we have an array that has the values 10, null, 30, and, 40 . TypeScript looks for the most common type to infer the type of the object. In this case, it picks the one thats is compatible with all types i.e. number, as well as null.
Consider another example:
var arr = [0, 1, "test"];
Here, the array has values of type number as well as type string. In such cases, the TypeScript compiler looks for the most common type to infer the type of the object but does not find any super type that can encompass all the types present in the array. In such cases, the compiler treats the type as a union of all types present in the array. Here, the type would be (string | number) which means that the array can hold either string values or number values. This is called union type.
Lets try to add a new element to the array:
var arr = [0, 1, "test"];
arr.push("str")
The compiler accepts the new value since the new value is of type string which is okay.
Now, lets try to add a new type to the array which was not already a part of the array:
var arr = [0, 1, "test"];
arr.push("str") // OK
arr.push(true); // Compiler Error: Argument of type 'true' is not assignable to parameter of type 'string | number'
The above code will show a compiler error because boolean is not a part of union (string | number).
The return type of a function is also inferred by the returning value. For example:
function sum(a: number, b: number )
{
return a + b;
}
var total: number = sum(10,20); // OK
var str: string = sum(10,20); // Compiler Error
In the above function, return type of the function sum is number. So, the result can be stored in a number type variable but not a string type variable.
Thus, type inference is helpful in type-checking when there are no explicit type annotations available.
Type Assertion in TypeScript
Here, you will learn about how TypeScript infers and checks the type of a variable using some internal logic mechanism called Type Assertion.
Type assertion allows you to set the type of a value and tell the compiler not to infer it. This is when you, as a programmer, might have a better understanding of the type of a variable than what TypeScript can infer on its own. Such a situation can occur when you might be porting over code from JavaScript and you may know a more accurate type of the variable than what is currently assigned. It is similar to type casting in other languages like C# and Java. However, unlike C# and Java, there is no runtime effect of type assertion in TypeScript. It is merely a way to let the TypeScript compiler know the type of a variable.
let code: any = 123;
let employeeCode = code;
console.log(typeof(employeeCode)); //Output: number
In the above example, we have a variable code of type any. We assign the value of this variable to another variable called employeeCode. However, we know that code is of type number, even though it has been declared as ‘any’. So, while assigning code to employeeCode, we have asserted that code is of type number in this case, and we are certain about it. Now, the type of employeeCode is number.
Similarly, we might have a situation where we have an object that has been declared without any properties yet.
let employee = { };
employee.name = "John"; //Compiler Error: Property 'name' does not exist on type '{}'
employee.code = 123; //Compiler Error: Property 'code' does not exist on type '{}'
The above example will give a compiler error, because the compiler assumes that the type of employee is {} with no properties. But, we can avoid this situation by using type assertion, as shown below.
interface Employee {
name: string;
code: number;
}
let employee = { };
employee.name = "John"; // OK
employee.code = 123; // OK
In the above example, we created an interface Employee with the properties name and code. We then used this type assertion on employee. Interfaces are used to define the structure of variables. Learn more about this in interface chapter.
Be careful while using type assertion. The TypeScript compiler will autocomplete Employee properties, but it won’t show any compile time error if you forgot to add the properties. For example:
interface Employee {
name: string;
code: number;
}
let employee = {
// Compiler will provide autocomplete properties,
but will not give an error if you forgot to add the properties
};
console.log(employee.name); // undefined;
You can also use the JavaScript library in TypeScript for some existing functions.
let employeeCode = myJSLib.GetEmployeeCode('Steve');
console.log(typeof(employeeCode)); // number
In the above example, we assume that myJSLib is a separate JavaScript library and we call its GetEmployeeCode() function. So, we set the type of return value as number because we know that it returns a number.
There are two ways to do type assertion in TypeScript:
1. Using the angular bracket <> syntax. So far in this section, we have used angular brackets to show type assertion.
let code: any = 123;
let employeeCode = code;
However, there is another way to do type assertion, using the ‘as’ syntax.
2. Using as keyword
let code: any = 123;
let employeeCode = code as number;
Both the syntaxes are equivalent and we can use any of these type assertions syntaxes. However, while dealing with JSX in TypeScript, only the as syntax is allowed, because JSX is embeddable in XML like a syntax. And since XML uses angular brackets, it creates a conflict while using type assertions with angular brackets in JSX.
TypeScript – Function Overloading
TypeScript provides the concept of function overloading. You can have multiple functions with the same name but different parameter types and return type. However, the number of parameters should be the same.
Function overloading with different number of parameters and types with same name is not supported.
function add(a:string, b:string):string;
function add(a:number, b:number): number;
function add(a: any, b:any): any {
return a + b;
}
add("Hello ", "Steve"); // returns "Hello Steve"
add(10, 20); // returns 30
II. TypeScript – Interfaces
Interface is a structure that defines the contract in your application. It defines the syntax for classes to follow. Classes that are derived from an interface must follow the structure provided by their interface.
The TypeScript compiler does not convert interface to JavaScript. It uses interface for type checking. This is also known as “duck typing” or “structural subtyping”.
1. Interface as Type
Interface in TypeScript can be used to define a type and also to implement it in the class.
The following interface IEmployee defines a type of a variable.
interface IEmployee {
empCode: number;
empName: string;
getSalary: (number) => number; // arrow function
getManagerName(number): string;
}
2. Interface as Function Type
TypeScript interface is also used to define a type of a function. This ensures the function signature.
interface KeyValueProcessor
{
(key:number, value:String): void;
}
function addkeyvalue(key: number, value: string) : void {
console.log('addKeyValue: key = ' + key + ', value = ' + value)
}
function updateKeyValue(key: number, value: string) : void {
console.log('updateKeyValue: key = ' + key + ', value = ' + value)
}
let kvp: KeyValueProcessor = addkeyvalue;
kvp(1, "ab")
kvp = updateKeyValue;
kvp(1, "cd")
3. Interface for Array Type
An interface can also define the type of an array where you can define the type of index as well as values.
interface NumList {
[index: number] : number
}
let numArr: NumList = [1,2,3];
numArr[0]
numArr[1]
interface IStringList {
[index: string] : string
}
let stringAr : IStringList;
stringAr["TS"] = "typescript"
stringAr["JS"] = "javascript"
In the above example, interface NumList defines a type of array with index as number and value as number type. In the same way, IStringList defines a string array with index as string and value as string.
4. Optional Property
Sometimes, we may declare an interface with excess properties but may not expect all objects to define all the given interface properties. We can have optional properties, marked with a “?”. In such cases, objects of the interface may or may not define these properties.
interface IEmployee {
empCode: number;
empName: string;
empDept?:string;
}
let empObj1:IEmployee = { // OK
empCode:1,
empName:"Steve"
}
let empObj2:IEmployee = { // OK
empCode:1,
empName:"Bill",
empDept:"IT"
}
In the above example, empDept is marked with ?, so objects of IEmployee may or may not include this property.
Read only Properties
TypeScript provides a way to mark a property as read only. This means that once a property is assigned a value, it cannot be changed!
Example: Readonly Property Copy
interface Citizen {
name: string;
readonly SSN: number;
}
let personObj: Citizen = { SSN: 110555444, name: 'James Bond' }
personObj.name = 'Steve Smith'; // OK
personObj.SSN = '333666888'; // Compiler Error
Extending Interfaces
Interfaces can extend one or more interfaces. This makes writing interfaces flexible and reusable.
interface IPerson {
name: string;
gender: string;
}
interface IEmployee extends IPerson {
empCode: number;
}
let empObj:IEmployee = {
empCode:1,
name:"Bill",
gender:"Male"
}
Implementing an Interface
Similar to languages like Java and C#, interfaces in TypeScript can be implemented with a Class. The Class implementing the interface needs to strictly conform to the structure of the interface.
interface IEmployee {
empCode: number;
name: string;
getSalary:(number)=>number;
}
class Employee implements IEmployee {
empCode: number;
name: string;
constructor(code: number, name: string) {
this.empCode = code;
this.name = name;
}
getSalary(empCode:number):number {
return 20000;
}
}
let emp = new Employee(1, "Steve");