Learn TypeScript fundamentals in 60-ish minutes - part(1)
JavaScript dynamic types are great. However, scaling large JavaScript applications often becomes difficult.
TypeScript is the superset of JavaScript with optional static types and it compiles to JavaScript. It is designed for the development of large applications. It may be used to develop applications for both client-side and server-side.
The static types provided are similar to popular statically typed languages such as Java, C# etc. If you are familiar with any of these languages then you might know how the type checking works during compilation. The static type checks also come with extra lines and lead to verbose coding.
Well, If you are the fan of static types, let’s dig in..
Basic Types:
Boolean
, Number
, String
, Array
, Tuple
, Enum
, Any
, Void
, Null
and Undefined
, Never
, Object
Syntax:
let variableName:type
//or inline initialization
let variableName:type = value;
A few examples below will help you understand the declaration of the variables of various basic types.
//Boolean
let isExpired:Boolean = false;
//Number:
let productCount:Number;
//String
let productName:String = 'detone';
//Template string can also be used as
let country:String = 'China';
let productDetails:String = `Country of origin ${country}`;
//Array
let productSizesInInches:number[] = [12,24,34]
//Array can also be written as
let prodcutSizesInInches:Array<number> = [12,24,34]
Tuple
Tuple is an array where the types are already known. The types do not need to be same.
/*declare an tuple which has items as number, number,
number ,string, and array of numbers*/
let preDefinedTypeTuple:[number, number, string, number[]];
preDefinedTypeTuple = [1,1,'1',[1,2,3,4]]
preDefinedTypeTuple = [1,"asd","1",[1,1,1,1]] //error
Enum
Enum is a way of giving names to sets of numeric values. By default, Numeric values start with 0(Zero). However, it can be preset to any specific value and following values would be an increment of 1(One). It is also possible to provide manual values to all fields.
Name of an enum value can also be looked up by its value
//Default Declaration:
enum Cities {Berlin , Beijing , California, London }
//usage:
let c:Cities = Cities.Beijing; // value would be 1
// Declaration with Initial Preset value:
enum Cities {Berlin =1 , Beijing , California, London }
c = Cities.Beijing; //value would be two
// Declaration with manual values:
enum Cities {Berlin =1 , Beijing =10 , California = 12, London = 0 }
c = Cities.Beijing; //value would be 10
//find name using the numeric value
let cityName:string = Cities[10]; //Beijing
Any
When the type is unknown or partially known, Any
can be used. It is a way of opting-out of type checking.
let foo:any = 14; // May be a number
foo = 'bar' //may be a string
foo = false // may be a boolean
foo.callMe(); // may be a object with callMe() method
Void
Void is absence of having a type. It is mostly used as a return type for the functions which does not return anything. If a variable has type Void, It can only be assigned Null
or undefined
.
Null and undefined are subtypes of all other types and can be assigned in place of any type.
Never
When the type of the value never occurs, It can be represented by Never
such as a function which throws an exception and will never return a value.
function foo():never{
throw new error("error message");
}
Object
Object is a type that represents the non-primitive type, i.e. any thing that is not number, string, boolean, symbol, null, or undefined
.
Type Assertion
It is basically typecasting. It is useful when a developer is sure that this particular variable is of a specific type and compiler is not able to infer the type.
// Declare an array of any type
let items:any = ["item1","item2","item3"]
//cast to an array of string
let itemSize:number = (<Array<string>>items).length
let name:any = "Dolce";
// cast to a string
let len:number = (<string>name).length;
Functions:
Functions can be declared as named functions or anonymous functions similar to JavaScript. However, TypeScript allows to have the type checking for function parameters and return type. Below is the syntax of the function.
/**
point function takes two arguments of number type and returns a number
**/
//named function
function point(x:number, y:number):number{
return x*Y;
}
//anonymous function
let point = function(x:number, y:number):number{
return x*Y;
}
Function types can also be declared. It consists of parameters
and return
types with return types being separated by fat arrow(=>
). Parameter names do not need to be consistent with the function type. Only the parameter types are checked. Return type declaration is mandatory. If a function does not return anything, return type should be void
,
TypeScript compiler normally infers the type if the type is present on one side of the equation. Below is an example of function type declaration and usage.
/**
declare a point as function type having parameters
as number type and the return type as number
**/
def point: (x:number, y:number) => number;
point = function(x:number,y:number):number{ x*Y };
//type is automatically inferred by compiler
point = function(x,y){ x*Y };
In TypeScript, Every function parameter is required. Although the values could be given as null
or undefined
. But it is mandatory to have equal number of arguments given to a function as the number of parameters function expects.
Optional Parameter
They are declared by adding a question mark(?
) to the name of the parameter. Optional parameters must follow the required parameters.
//function type
(firstName:string, lastName?:string) => string
//Declaration
function user(firstName: string, lastName?: string):string{
return firstName+lastName;
}
Default Parameters
Default parameters are assigned a value during function declaration. Default parameters following after all the required parameters are considered optional and can be omitted during the function call. As a result, In the function type, default parameters are mentioned similar as optional parameters. During the function call, if the argument corresponding to the default parameter has an undefined
value or the argument is not provided, the default value will be considered. Default parameters can be provided in any order. In case, required parameters follow default parameters, function call should explicitly have undefined
for default parameters.
//function type is similar as option parameter
(firstName:string, lastName?:string) => string
//declaration
function User(firstName:String,lastName:String = "dash"){
console.log(`Name = ${firstName} - ${lastName}`)
}
Trailing default parameters and optional parameter can be omitted from the function type.
Rest Parameters
It is similar to arguments
in JavaScript and varargs
in Java.They are represented by preceding ellipsis such as ...restparam
. Rest parameters are treated as the boundless number of parameters. Any number of arguments can be passed to it. You can even pass none. The compiler builds up an array of arguments
and can be accessed inside the function with the name following ellipsis.
//return type is inferred as string type here by the compiler
function addStudents(...students:string[]){
return students.join('-');
}
Function Overloading
Unlike Java or C#, where function overloading consists of multiple declarations of the same function with a different number or type of parameters. In TypeScript, the overload list
is provided. The overload list is the possible number of function variations to be allowed in an overloaded function. The final function declaration contains parameter types and return types of which the overloaded list would make a subset. Here is a detailed post.
//overload list type include Number and String
function add(x:number, y:number):number;
function add(x:string, y:string):string;
//final declaration contains type Any which is superset to all types
function add(x:any, y:any){
return (typeof x === 'string' && typeof y === 'string') ? `${x}-${y}` : x+y
}
//you can also make the final declaration more specific by using union types.
function add(x:number|string , y:number|string):number|string{
return (typeof x === 'string' && typeof y === 'string') ? `${x}-${y}` : x+y
}
add("star","awards"); //pass
add(1,2); //pass
add([1,2,3],[1,2,3]); //fail
Interfaces:
The interface represents the shape of the values. It is a way of defining contracts in the code to avoid any type mismatch. Interfaces can be used to specify types of a group of values, function types, index types(arrays) and class types
A simplified way of describing an object would be as following:
// Specify types for a javaScript object
let user:{name:string, age:number, height:number} = {name:'irshad',height:198,age:30};
//user is of the type UserType
let user:UserType = {name:'irshad',height:198,agee:30};
Interfaces can represent an object with properties
/*Interface describes an object with properties.
Here UserType represents a type of object with memebers such as
name(string type), age(number type), height(number type).
*/
interface UserType{
name:string,
age:number,
height:number
};
//user is of the type UserType
let user:UserType = {name:'irshad',height:198,agee:30};
//error. location is not specified in the type
let user:UserType = {name:'irshad',height:198,location:30.01};
The required parameters should exist and types should match. The compiler does not check for the order of parameters. we will discuss the optional
and readOnly
parameters later in the tutorial.
Interfaces can represent function types
//userType interface represents a function type
interface userType{
//function type is represented by set of parameters and return value
(firstName:string, lastName:string, age:number):string;
}
/**username is a userType. Notice the function declaration doesn't
contain explicit parameter types or return types.
It is due to type inferrence.
**/
let userName:userType = function(first,last,age){
return `${first} - ${last}`
}
//provide explicit types for function declaration
userName = function(first:string,last:string,age:number):string{
return `${first} - ${last}`
}
userName('irshad',"ahmad", 30); //pass
userName('irshad',"ahmad", "30"); //fail.
For the function types,The compiler considers the order in function parameters. It matches types of one parameter at a time. If the types are not specified in function declaration, the contextual typing of TypeScript can infer the types of arguments during the function call.
Interfaces can represent Indexable types
Indexable types have index signatures which take a form of a[10]
or a['ten']
and return a value. A rule of thumb is indexer type and return type of an indexable type will never change i.e if indexer is of type X and its return type is Y. It will always remain so.
/** declare dot as an interface for the indexable type with
* indexer of type number and return type as string */
interface dot{
[x:number]:string
}
let point:dot [100];
string
and number
are the supported index types. Number indexes are internally transformed into string types in JavaScript. As a result, it is possible to use both the types together for indexes. However, the return types should either be same or type returned from a numeric indexer must be a subtype of the type returned from the string indexer.
/** both string and numeric indexer is used and return type is a string in both cases */
interface dashIndexTYpe{
[x:number]:string
[x:string]:string
}
//Not valid as return types are not same
interface dashIndexType{
[x:number]:string
[x:string]:number
}
let a:dashIndexTYpe = {'a':'100',2:'100'}
An Object oriented example :)
class road{ name:string}
//class highway is subtype of class road
class highway extends road{ line:number}
/** valid
* numeric indexer type returns the similar datatype as string
*/
interface roadIndexableType{
[x:string]: road
[x:number]: road
}
/** valid
* numeric indexer type returns the subtype
* of string indexer return type
*/
interface roadIndexableTypeD{
[x:string]: road
[x:number]: highway
}
/**
* Not Valid
* Numeric indexer return type is not similar to
* or subtype of string indexer return type
*/
interface roadIndexableTypeNV{
[x:string]: highway
[x:number]: road
}
Classes implement Interfaces
Interfaces can be implemented by classes just like in Java
or C#
//Declare a person Interface
interface person{
name:string
getAverageScore(...scores:number[]):number;
}
// A class implements the interface person and
// inherits the methods and properties declared
class BachelorStudents implements student{
constructor(){}
name:string;
getAverageScore(...scores){
let avarge:number;
//do some logic
return avarge;
}
}
let bc1 = new BachelorStudents();
Interfaces extend to Interfaces
Interfaces can extend other interfaces and inherit their properties or methods. The class implementing the last interface has to implement methods present in the interfaces to which the current interfaces extends
to.
interface person{
name:string
}
interface student extends person{
getAverageScore(...scores:number[]):number;
}
class BachelorStudents implements student{
constructor(){}
name:string;
getAverageScore(...scores){
let avarge:number;
//do some logic
return avarge;
}
}
let bc1 = new BachelorStudents();
Hybrid Types
Since JavaScript is dynamic and flexible. It is possible for an interface to represent more than one kind of type at the same time.
An example below describes how an object can behave as a function as well as an object at the same time.
/** Declare an interface as a composite type with
members as a function type and a property */
interface Trip{
//function type
(place:string)
//property distance
distance:number
}
//function return the object type as counter
function getTrip():Trip{
let c = <Trip>function(place:string){}
c.distance = 100
return c;
}
let app = getTrip();
app('call function with place argument');
app.distance = 102
Optional and readonly types
The properties present in an Interface which may not be required can be declared as optional. An optional property can be declared by appending ?
after the name
interface Person{
name: string
age?: number
phone?: number
}
function createUser(p:Person){
console.log("Name: ",p.name);
//it is important to validate optional params
if(p.age){
console.log("Age: ",p.age);
}
if(p.phone){
console.log("phone: ",p.phone);
}
}
Readonly properties can only be modified when the object is created. A readonly property is specified by putting readonly
before the name. It is similar as is a const
for a variable. However, both have different usage.
interface readOnlyPerson{
readonly name: string
}
let person1:readOnlyPerson = {name :"Bolt"};
console.log("Name of person1", person1.name);
//upadate failure
person1.name = "usian"