npm install -g typescript
tsc --version
(tsc stands for TypeScript compiler)code main.ts
function log(message) {
console.log(message);
}
var message = 'Hello World';
log(message);
tsc main.ts
ng serve
in Angular, the transpilation takes place and run automaticallynode main.js
function doSomething(){
for (var i=0; i<5; i++){
console.log(i);
}
console.log('Finally: ' + i);
}
doSomething()
Note the var i declared inside the for block is still available outside the for block !
If we use let
instead of var
in the for block and save it, we immediately encounter a problem Cannot find name 'i'
.
This is the compile error from TypeScript. The beauty of detecting an error early before we deploy/run our app!
The let
keyword limit the scope of variable i to the nearest block instead of the nearest function.
If we compile "main.ts" in the terminal tsc main.ts
, the compiler show the message Cannot find name 'i'
as well but the compiler will still generate the main.js
file!
If we examine the "main.js" file, we can find that the change made on variable i let i
is restored to var i
. This is because by default, TypeScript compiler compiles our TypeScript code to ES5, which is the older version of Javascript. In ES5, there is no let
keyword.
JavaScript Versions
TypeScript compiler reports errors, but still generates valid JavaScript code
Declare variables using the
let
keyword**
We can do the above in JavaScript but not in TypeScript. TypeScript compiler immediately gives you an error type '"a"' is not assignable to type 'number'
If you declare a variable with let
without initializing it, the type will be any
and even the TypeScript compiler won't complain about it.
let a;
a = 1;
a = true;
a = 'a';
If we don't know the value of the variable ahead of time, we can use type annotations
let a: number;
The type number
includes any integer and floating point numbers
let a: number; // integer & floating point numbers
let b: boolean;
let c: string;
let d: any;
let e: number[]; //array of numbers
let f: any[] = [1, true, 'a', false]; //not a good practice
Let's say you're working on a group of related constants, like colors
// Vanilla JS
const ColorRed = 0;
const ColorGreen = 1;
const ColorBlue = 2;
In OOP, we can put all related constants in a container so that we don't have to remember the details.
enum Color {Red, Green, Blue};
let backgroundColor = Color.Red;
By default, the values of the enum type are 0, 1, 2 respectively
Color.Red
It's a good practice to explicitly specify the value of the enum type because if a new element is added in the future, it won't break parts of the app.
enum Color {Red = 0, Green = 1, Purple = 3, Blue = 2};
When we compile the TS file, we can view the implementation of enum in Javascript:
any
any
, we can't use InterliSense in VS code// Type Assertion
let message;
message = 'abc';
let endsWithC = (<string>message).endsWith('c');
endsWithC
// Alternative
let alternativeWay = (message as string).endsWith('c');
alternativeWay
// Vanilla JS
let log = function (message){
console.log(message);
}
// Arrow Function
let doLog = (message)=>{
console.log(message);
}
// Arrow Function (1 line)
let doLogOneLine = (message) => console.log(message);
If the function can be written as a one-line arrow function, we can't skip the ()
that surrounds the parameter but it's not advised to do so.
If the function has no parameter, we still need the bracket:
// Arrow Function (no params)
let doLogOneLine = () => console.log();
The following function works but it's too verbose:
let drawPoint = (x, y, w, a, b, c) => {
//...
}
A better approach is to wrap every params in one object in the function definition:
let drawPoint = (point) => {
//...
}
drawPoint({
x:1,
y:2
})
However, the above approach can't prevent passing in an invalid object
drawPoint({
name: 'Peter',
y: 2
})
To validate the object passing in the function, we can use inline annotation:
let drawPoint = (point: {x: number, y: number}) => {
//...
}
However, inline annotation looks too verbose if the object is large with many properties. Also, the same object may be used by other functions as well so it's difficult to maintain the code.
A better alternative is to use an interface which can make the object reusable and the function definition cleaner. Note that by convention, we capitalize the name of the interface:
interface Point {
x: number,
y: number
}
let drawPoint = (point: Point) => {
//...
}
However, there is a problem with this approach
interface Point{
x: number,
y: number
}
let drawPoint = (point: Point) => {
//...
}
drawPoint({
x: 1,
y: 2
})
Class
properties
and fields
(more on this later)class Point{
x: number;
y: number;
draw(){
//...
}
}
new
operator to create an object from a class// Instantiate a point object
let point = new Point();
point.x = 1;
point.y = 2;
point.draw();
?
after the argumentsclass Point{
x: number;
y: number;
// Constructor with optional parameters
constructor(x?: number, y?: number){
this.x = x;
this.y = y;
}
draw(){
console.log('X: ' + this.x + ' Y: ' + this.y);
}
}
// Declare object without initial value
let point = new Point();
point.draw();
public
and public
and private
are the most commonprivate
, we can't access the memeber via InterlliSenseclass Point{
private x: number;
private y: number;
constructor(x?: number, y?: number){
this.x = x;
this.y = y;
}
draw(){
console.log('X: ' + this.x + ' Y: ' + this.y);
}
}
let point = new Point(1, 2);
point.draw();
private
access modifer on the fields x and ypublic
and can be accessible from the outsideclass Point{
constructor(private x?: number, private y?: number){
}
draw(){
console.log('X: ' + this.x + ' Y: ' + this.y);
}
}
property
get
or set
and then the name of the propertyGetter
and Setter
A property looks like a field from the outside, but internally it's really a method in a class
class Point{
constructor(private x?: number, private y?: number){
}
draw(){
console.log('X: ' + this.x + ' Y: ' + this.y);
}
// Property X aka 'Getter'
get X(){
return this.x;
}
// Property X aka 'Setter'
set X(value){
if (value < 0)
throw new Error('value cannot be less than 0.');
this.x = value;
}
}
We can use these properties like fields
We can read X and set X with cleaner syntax:
let x = point.X;
point.X = 10;
Camel Casing Notation
to name our field i.e. the first letter of the first word is lowercase, and the first letter of the every word after is uppercaseF2
Enter
// Camel Casing Notation
class Point{
constructor(private _x?: number, private _y?: number){
}
draw(){
console.log('X: ' + this._x + ' Y: ' + this._y);
}
// Getter
get x(){
return this._x;
}
// Setter
set x(value){
if (value < 0)
throw new Error('value cannot be less than 0.');
this._x = value;
}
}
let point = new Point(1, 2);
let x = point.x; // cleaner syntax
point.x = 10; // cleaner syntax
point.draw();
// File: main.ts
class Point{
constructor(private _x?: number, private _y?: number){
}
draw(){
console.log('X: ' + this._x + ' Y: ' + this._y);
}
}
let point = new Point(1, 2);
point.draw();
class Point{
constructor(private _x?: number, private _y?: number){
}
draw(){
console.log('X: ' + this._x + ' Y: ' + this._y);
}
}
export
keywordmodule
// point.ts (module)
export class Point{
constructor(private _x?: number, private _y?: number){
}
draw(){
console.log('X: ' + this._x + ' Y: ' + this._y);
}
}
import
this class so we can use it:Point
- name of the types./point
- name and path of the module// main.ts (module)
import { Point } from './point';
let point = new Point(1, 2);
point.draw();
When we have an import
or export
statements on top of a file, that file is a module
from TypeScript's point of view
In Angular, we also have the concept of module but it's a little bit different than TypeScript