5. Know your environment: the tool chain
Programming in EcmaScript 2015+ and TypeScript is quite unlike programming in
JavaScript. With the latter in theory you could use just the browser and Notepad.
Browser support for EcmaScript is still limited and absent in case of TypeScript. So
you will need a compiler and a whole tool chain to work with these languages.
And more: in practice you will need to know, and know well:
Visual Studio Code
(or another IDE like Webstorm or Atom)
a whole series of plugins for the IDE
the TypeScript compiler (tsc, ts-node)
(or Babel & Flow)
Node inspector (V8 debugger) and/or Chrome Developer Tools
(and corresponding tools)
node.js
TSLint (“linter” or style and error checker)
Yarn, npm (package management)
Mocha, chai (for testing)
Git (Source Code Version Control)
TypeDoc (Documentation Tool)
7. let is the new var
Use let instead of var
Block scope vs function scope
No more “hoisting”
//compiles fine
function functionscope(){
for(i=0;i < 10; i++){
console.log(i)
}
if (true){
var i = 10000
}
console.log(i)
}
//gives error TS2304: Cannot find name 'i'.
function blockscope(){
//let i;
for(i=0;i < 10; i++){
console.log(i)
}
if (true){
let i = 100000
}
console.log(i)
}
functionscope()
blockscope()
src: letisthenewvar.ts
8. let is the new var; fixes problem with closures
Using let fixes a longstanding problem with closures
block scoped references captured in a closure will maintain the value at the moment of capture
That is NOT the case with var (the reference will point to the last value it was set to)
for(var j= 0; j < 10; j++){
setTimeout(()=> console.log(`setTimeout with var: ${j}`),100)
}
for(let k= 0; k < 10; k++){
setTimeout(()=> console.log(`setTimeout with let: ${k}`),100)
}
src: letisthenewvar.ts
9. let is the new var (and so is const)
Use const when you want to denote
immutability of a reference (the
“variable”). Use readonly for object
properties.
Notice that objects and arrays are not
const. Only their references.
Use Object.freeze or Immutable.js (a
library) for this.
No compile time, type level, support
for nested immutability
const co = 100
//gives error TS2540: Cannot assign to 'co'
because it is a constant or a read-only property.
//co = 1000
const co2 = {a: 100}
co2.a = 10000000000000000
console.log(co2)
Object.freeze(co2)
//RUN-TIME ERROR: TypeError: Cannot assign to read
only property 'a' of object '#<Object>'
co2.a = 0
src: letisthenewvar.ts
10. Modules
export function hello() {
return 'Hello World!'
}
export function hola(){
return 'Hola mundo!'
}TypeScript and ECMAScript 201x have a
concept of modules. Any file containing
a top-level import or export is
considered a module.
Modules are executed within their own
scope, not in the global scope; this
means that variables, functions, classes,
etc. declared in a module are not visible
outside the module unless they are
explicitly exported using one of the
export forms. Conversely, to consume
an export (variables, etc.) from a
different module, it has to be imported
using one of the import forms.
Examples can be found on the right:
import { hello } from '../app/services';
import { hola as spanishhello } from '../app/services';
import * as greetings from '../app/services';
import Msg from '../app/defaultservices'
console.log(hello())
console.log(spanishhello())
console.log(greetings.hello())
let msg = new Msg()
console.log(msg.hola())
export default class {
hello() {
return 'Hello World!'
}
hola(){
return 'Hola mundo!'
}
}
src: testimports.ts services.ts defaultservices.ts
11. Unit Testing
Unit testing in TypeScript in general
takes the form of automatic testing
of TS modules together with
associated control data to determine
whether these modules work
excepted.
With mocha as testing framework or
test runner, and chai and assertion
library, it is easy to integrate (a form
of) TDD or BDD within each TS
project.
import { expect } from "chai"
// if you used the '@types/mocha' method to
install mocha type definitions, uncomment the
following line
import "mocha"
import { hello } from "../app/services"
describe("Hello function", () => {
it("should return hello world", () => {
const result = hello()
expect(result).to.equal("Hello World!")
})
})
src: test.ts
13. TypeScript´s Type System
The type system in TypeScript is designed to be
optional so that your JavaScript is
TypeScript.
TypeScript does not stop generating JavaScript
code in the presence of type errors, allowing you
to progressively update your JS to TS.
But that does mean that TypeScript is not “safe”
But --noEmitOnError flag prevents generating
JavaScript code when there are Type errors
Therefore: Types are annotations; they don´t
“exist” run-time (type erasure)
let foo = {};
foo.bar = 123; // Error:
property 'bar' does not exist on
`{}`
console.log(foo.bar)
foo.baz = "ABC" // Error:
property 'baz' does not exist on
`{}`
console.log(foo.baz)
c
src: typesdontexist.ts
14. Type annotation syntax
Type annotations are optional
They can be added after any
expression
Classes and interface are first class
citizens within the type system (but be
careful for comparisons with Java; see
structural typing)
Function parameters and the function
result are typed. But note that
functions themselves also have a type.
The type keyword can be used to
declare a type alias
let x : number
x = 10
let y : number = 10
interface Named {
name: string
}
class M implements Named{
name: string
constructor(name:string){
this.name = name
}
}
let m : Named = new M("Jorge")
console.log(m.name)
let double = function(a: number): number{
return a + a
}
type Doubler = (a:number) => number
let doStuff = function(callback: Doubler){
//do LOTS of stuff
console.log(callback(100))
console.log(callback(100))
console.log(callback(100))
}
doStuff(double)
src: typeannotations.ts
15. Type inference
let fullName = `Perico Palotes`
let age = 37
let sentence = `Hola, me llamo ${ fullName }`
console.log(sentence)
// error TS7006: Parameter 'name' implicitly has an 'any' type.
//let englishName = function(name){
let englishName = function(name : any){
let sentence = `Hello, my name is ${ name }`
console.log(sentence)
}
englishName(fullName)
englishName(1000) // Hello, my name is 1000
let englishName2 = function(name: string){
let sentence = `Hello, my name is ${ name }`
console.log(sentence)
}
// englishName2(1000) Compile error
In modern statically typed
programming languages like
TypeScript, it is not necessary to
always declare the type of a
variable. The compiler can infer
the type of the expression.
In TypeScript the type of function
parameters without type
annotations is inferred to be of
type any. This can result in errors
when compile flag –noImplicitAny
is set
src: typeinference.ts
16. --strict (TypeScript 2.3)
flag Meaning
--alwaysStrict Parse in strict mode and emit "use strict" for each source file
--noImplicitThis Raise error on this expressions with an implied any type.
--strictNullChecks Strict null checking mode, the null and undefined values are not in the domain of every
type and are only assignable to themselves and any (the one exception being
that undefined is also assignable to void).
--noImplicitAny Raise error on expressions and declarations with an implied any type.
--noImplicitReturns Report error when not all code paths in function return a value.
… more …
TypeScript supports a whole series of “compiler flags” which can be set in
tsconfig.json. From version 2.3 on, all flags related with “strict” compilation
can be set in with the –strict flag. In 2.2 and below the flags need to be set
individually.
src: strict.ts
17. Type inference, arrays and Tuples
let arr1 = [0,1,2]
//Not inferred to be a Tuple, but rather an array of
elements of two possible types: number OR String
let arr2 = [500,"Internal Server Error"]
console.log(arr2)
//compile error
//let tuple : [number, string] = arr2
type HTTPError = [number, string]
let error1 : HTTPError = [500,"Internal Server Error"]
//Type Asseryion (not "cast")
let error2 = [500,"Internal Server Error"] as HTTPError
console.log(error1, error2)
Tuple types allow you to express
a type, effectively an array of
fixed size, where the type of a
fixed number of elements is
known, but need not be the
same.
As the literal syntax for Arrays
and Tuples are identical, Tuple
types cannot be inferred
correctly.
src: typeinference.ts
18. Contextual inference
Type inference also works in
"the other direction" in
some cases in TypeScript.
This is known
as "contextual typing".
Contextual typing occurs
when the type of an
expression is implied by
its location.
import { readFile } from "fs"
readFile("d:/tmp/data.txt", function(err,buf){
console.log(buf.toString())
})
src: contextual.ts
19. Best practice
DRY and don´t be verbose: use type inference
let s1 = "lala"
/*NOT*/
let s2: string = "lala"
Avoid the implicit usage of any
--noImplicitAny
Type the parameters of your own non call-back functions.
Optionally type the return value of the function
function translate(text:string):string {
return `¿Eh? ¿Que signifique '${text}'?`
}
20. Destructuring
let node = {
type: "Identifier",
id: "foo"
};
let { type, id } = node
console.log(type) // "Identifier"
console.log(id) // "foo”
// compile error when --noImplicitAny is set
// alternative:
// let showName = function( { id } :
{id:string} ){
let showName = function( { id } ){
console.log(id)
}
showName(node); // "foo”
ECMAScript 201x simplifies data
access by adding destructuring, which
is the process of breaking down a data
structure into smaller parts.
This is extremely powerful and useful.
And it´s an essential, integral part of
the core features of the language
(modules).
But be careful, because of TypeScript´s
type system, destructuring can be a bit
different in “feel” between the two
languages.
src: destructuring.ts
21. Destructuring: arrays and nesting
Array destructing is syntactically
different from Object
destructuring but follows the
same principle.
The remaining items in a list can
be caught in a variable for the
using the syntax ...name
Complex destructuring patterns
can arise with nested objects,
Tuples and arrays.
let input = [1, 2];
// create NEW variables first and second
let [first, second] = input;
console.log(first); // outputs 1
console.log(second); // outputs 2
// swap variables
[first, second] = [second, first];
//remaining items in a list using the syntax ...name :
let [firstelement, ...remainder] = [1, 2, 3, 4];
console.log(firstelement); // outputs 1
console.log(remainder); // outputs [ 2, 3, 4 ]
//Nested destructuring of Tuples/arrays. Only have a binding for the named item
let justanarray : [string, boolean, number[]] = ["data",true,[3,4]]
let [,,[,elem2]] = justanarray
console.log(`Nested element in array ${elem2}`) // outputs 4
let nestedobject = {anumber: 100, nested: {deep: "ok"}}
let {nested: {deep}} = nestedobject
//compile error: only names "on the right" of the expression are declared
//console.log(nested)
console.log(deep)
//variable renaming; confusing syntax!
let {nested: {deep: deepest}} = nestedobject
console.log(deepest)
src: destructuring.ts
22. Structural typing
Type compatibility in TypeScript
is based on structural
(sub)typing.
Structural typing is a way
of relating types based solely on
their members.
This is in contrast with nominal
type systems like that of C# and
Java
interface Named {
name: string;
}
class Person { // no ‘implements’
name: string;
constructor(name:string){
this.name = name
}
//constructor(public name: string){}
}
let p: Named;
// OK, because of structural typing
p = new Person("Hagard");
console.log(p)
// also with anonymous objects
p = { name: "Olaf" }
console.log(p)
src: typecompat.ts
23. Type inference with assignment & declarations
There is a difference in the
behaviour of type inference
between declarations (“let”) with
their corresponding initializers
and assignment.
In the example assignment works
but the variable initializer must
exactly match the structure of
Named
interface Named {
name: string;
}
let p: Named;
// also with anonymous objects
let o = { name: "Olaf" , surname:
"Leifson"}
p = o;
console.log(p)
// compile error
//let n : Named = { name: "Olaf" ,
surname: "Leifson"}
src: typecompat.ts
24. Decorators
A Decorator is a special kind of declaration that can be
attached to a class declaration, method, accessor, property,
or parameter. Decorators use the form @expression, where
expression must evaluate to a function that will be called at
runtime with information about the decorated declaration.
It is a form of meta-programming which follows the model
as set by Decorators in Python, i.e. they are actively
executing functions, rather than the model followed by
Annotations in Java (objects as passive meta-data)
The application of Decorators is an “experimental” feature
of EcmaScript / TypeScript. It may change but it is unlikely to
disappear (as many important libraries like Angular are
already depending on it).
Decorators are an advanced topic and extremely powerful
but also very “magical”, complex and therefore fraught with
danger. Use with care.
function log(target: any, key: string, value: any) {
return {
value(...args: any[]) {
const result = value.value.apply(this, args)
console.log(`method: '${key}' called with arguments
'${args}' and with result: '${result}'`)
return result
}
}
}
class Demo {
@log
public say(...args: string[]) {
console.log("Inside say with arguments: ", args)
return 100
}
}
let d = new Demo()
d.say("Booh", "Lala")
d.say("Bah")
src: decorators.ts
25. What´s left:
Async – await
Symbols
Iterators & generators
Discrimitated Unions
Mixins
and much more …