Skip to content
No description, website, or topics provided.
Branch: master
Clone or download
Fetching latest commit…
Cannot retrieve the latest commit at this time.
Permalink
Type Name Latest commit message Commit time
Failed to load latest commit information.
images
.gitignore
README.adoc
app.js
app.ts
index.html
package-lock.json
package.json
tsconfig.json Refactor to new structure + add setup instructions with lite-server May 14, 2019

README.adoc

Typescript Workshop

These are notes and code snippets from the Udemy Workshop Understanding TypeScript. Although this repository here uses code snippets from the Udemy Workshop, the original course is very much recommended because it’s great and has additionally sections not covered in this repository like workflows with gulp and webpack and using TypeScript together with ReactJS.

I left out some explanations and examples because for me as a Java developer, TypeScript is very similar to Java. Hence, this is not a workshop to learn TypeScript from scratch without knowing at least one other object oriented language.

1. Introduction

  • = superset of Java Script, which is a new statically, strongly-typed programming language on top of Java Script

  • doesn’t run in the browser, has to be compiled - that’s what the CLI is for!

  • type can either be stated explicitly:

    serverId:number = 10;
  • type can also be omitted and chosen automatically:

    serverId = 10;
  • with Ctrl + B, variables reveal their types:

typeCheckingWithCtrlB
  • ES6 also brings types, but not yet supported by all browsers

2. Installation

  1. install NPM

  2. install TypeScript via

npm -g install typescript

3. Compile TypeScript to JavaScript

  1. navigate to where your ts file is located

  2. compile with filename without ".ts", so for example to compile script.ts, do:

tsc script
  • watch-mode = compile every change automatically:

tsc -w

4. Setup for this workshop

  • put project folder under control of NPM:

npm init
  • will create a package.json

  • install lite-server:

npm install lite-server --save-dev
  • add script to start server (in package.json):

"scripts": {
    ...
    "start": "lite-server"
  • start server with

npm start
  • server will automatically react on changes in the codebase

  • application on localhost:3000

  • make folder managed by TypeScript with

tsc --init
  • will create a tsconfig.json-file

  • that way, all TypeScript files will be compiled with only

tsc
  • attention: keep lite-server running in different terminal-tab!

5. Types

// implicit declaration:
let myString = 'myString';   // implicit string
//myString = 42;             // error!

let myNumber = 42;           // implicit number
//myNumber = 'anyString';    // error!

let myBoolean = true;        // implicit boolean
//myBoolean = 1;             //error, despite the fact that "1" could be interpreted as "true"

let myVariable;              // type = 'any', from JavaScript
myVariable = 12;             // OK
myVariable = '12';           // OK because 'any' gets overridden

// explizit declaration:
let myExplicitNumber: number;
myExplicitNumber = 12;       // OK
myExplicitNumber = '12';     // error!

let myArray = ["bla", "blubber"];
console.log(typeof myArray);        // prints "object"; however: TypeScript only allows strings in this array ...
myArray = [42];              // ... this will cause an error
myArray = ["42"];            // OK

let myArray: string[] = ["bla", "blubber"];     // explicit array of strings

// Tuples = new type in TypeScript (not available in JavaScript)
let address: [string, number] = ["aString", 42];

// Enum
enum Color {
    Red,
    Green,
    Blue
}
let myColor: Color = Color.Red;

// Internally represented as number (0-based). Can be configured differently:
enum Color {
    Red = 42,
    Green = 43,
    Blue = 44
}

// any => use only in exceptional cases!
let blubber: any = "aString";
blubber = 42;           // OK

// Functions
function getSomeString(): string {
    return "some string";
}

function noReturnValue(): void {
    //return "some string";       // error because no return expected
}

// Argument Types
function myFunction(v1: number, v2: number): number {
    return v1 + v2;
}

// Function Types
let myFunctionAsAVariable: (val1: number, val2: number) => number;
myFunctionAsAVariable = myFunction;
myFunctionAsAVariable(1, 2);

 let myFunctionAsAVariable2: () => void;
 myFunctionAsAVariable2 = noReturnValue;

// Objects
let myData = {
    aString: "myString",
    aNumber: 42
};

myData = {};
// error: "not assignable" because TypeScript automatically assigned a type with the two attributes (aString and aNumber)

myData = {
    anotherString: "myString",
    anotherNumber: 42
};
// error: names don't match!

let myData: {aString: string, aNumber: number} = {
    aString: "myString",
    aNumber: 42
};

// Type Alias
// = storing a type; alternative  to class
type MyType = {aString: string, aNumber: number};
let x: MyType = {
    aString: "blubber",
    aNumber: 12
}

// Union Types
// sometimes more than one type should be appliable, but not just "any"
let someUncertainInput: any = 12;
someUncertainInput = "12"           // OK
someUncertainInput = false          // OK, but only number or strings should work

let someUncertainInput2: number | string = 12;
someUncertainInput2 = "12"           // OK
//someUncertainInput2 = false          // error

// Check Types
let value = "a string";
if(typeof value == "string") {
    // ...
}

// New Types (since TypeScript 2.0)
// 1. "never", when a function never returns:
function neverReturns(): never {
    throw new Error("blubber");
}

// 2. nullable types:
let canBeNull = 12;
canBeNull = null;       // OK

// in tsConfig.json:
// "strictNullChecks": true

let canBeNull = 12;
canBeNull = null;
// error: 'null' is not assignable to type 'number' because canBeNull was initialized to be a (not-nullable) number

let canBeNull: number | null = 12;
canBeNull = null;       // OK again

6. TypeScript Compiler

6.1. Types

  • types are removed in JavaScript!

  • default behavior of TypeScript compiler: compile to JavaScript, even when there are errors

  • compiling despite errors can be disabled in tsconfig.json with

"noEmitOnError": true

6.2. SourceMaps

  • mapping between TypeScript and JavaScript

  • enable in tsconfig.json with:

"sourceMap": true
  • with that, app.js.map is created

  • used by browser to enable debugging

6.3. noImplicitAny

let anything;       // will get type "any"
anything = 12;
  • type of any automatically assigned

  • can be disabled in tsconfig.json with:

"noImplicitAny": true
  • now, compiler will throw error for above code

  • forces programmer to use proper types

7. ES6

  • TypeScript supports many features of ES6

7.1. Variable Declaration: var, let, const

  • three options for declaring variable: var, let, const

7.1.1. var

  • spoiler alert: least preferable from the three options

  • traditional the way to declare a variable in JavaScript

  • available in TypeScript because TypeScript = superset of JavaScript

  • some odd "features" like "var-scoping": declarations of var are accessible anywhere, even globally. Details see here

  • global scope

7.1.2. let

  • introduced because of the problems with var

  • block-scoped = not visible outside of the block let was defined in

  • behavior = expected behavior when coming from Java

7.1.3. const

  • = augmentation of let; prevents re-assignment

  • principle of least privilege: const should be used whenever re-assignment of variable is not intended

let myVariable = "blubber";
myVariable = "another blubber";     // OK

const anotherVariable = 100;
//anotherVariable = 200;              // error

function reset() {
    let myVariable = "blubber in function";
    console.log(myVariable);        // "blubber in function"
}
reset();
console.log(myVariable);            // "another blubber"

7.2. Arrow Functions

// normal function:
const addNumbers = function(number1: number, number2: number): number {
    return number1 + number2;
}

// arrow function short syntax:
const multiplyNumbers = (number1: number, number2: number) => number1 * number2;

// arrow function long syntax:
const multiplyNumbers = (number1: number, number2: number) => {
    // do something else here
    return number1 * number2;
}

// one argument:
const doStuff = myVariable => console.log(myVariable);

// without arguments:
const doLog = () => {
    console.out("log");
}

7.3. Default Parameters

const simpleFunction = (myParameter: number = 1): void => {
    console.out(myParameter);
}
simpleFunction(42);     // OK - will print 42
simpleFunction();       // OK - will print 1

7.4. Rest & Spread Operators

  • same syntax ("…​") for two different use cases:

    • spread-operator used when function is called to spread out array

    • rest-operator used in function signature to aggregate list of values to an array

const numbers = [1, 2, 3];
Math.max(4, 5, 6);      // OK
Math.max(numbers);      // error because no array allowed here

// spread-operator spreads the contents of the array into a list of values:
Math.max(...numbers);   // OK

// rest-parameter: function that gets list of numbers as parameters and returns an array:
function makeArray(...args: number) {
    return args;
}
makeArray(1, 2, 3);     // OK
  • attention: in a function where some parameters that should NOT be combined and some that should be combined: combine-parameters have to be the last ones!

  • since TypeScript 3, rest operator working also with tuples:

function foo(...myObject: [number, boolean]) {
    // ...
}

7.5. Destructuring

  • instead of picking every single array element one by one, all elements can be extracted from an array:

const myArray = [1, 2, 3];
const [number1, number2, number3] = myArray;
  • result:

    • number1 is 1,

    • number2 is 2,

    • number3 is 3

  • works also for objects:

const myObject = {foo: "foo", bar: 42};
const {foo, bar} = myObject;
  • result:

    • foo is "foo"

    • bar is 42

  • also possible: renaming variables:

const myObject = {foo: "foo", bar: 42};
const {foo2, bar2} = myObject;
  • result:

    • foo is undefined

    • bar is undefined

    • foo2 is "foo"

    • bar2 is 42

7.6. Template Literals

  • = strings with more features

  • created with ``

const myString = "myString";
const message = `Here is a message.
It's multilined!
Here is another string: ${myString}.
`;

8. Classes

  • also possible to create classes in ES6, but with less features like private properties

  • private properties only accessible within the object; protected attributes additionally accessible in every object that inherits this object

class Person {
    name: string;
    private type: string;
    protected age: number;

    constructor(name: string, public username: string) {
        this.name = name;
    }

   printAge() {
        console.log(this.age);
   }

   setType(type: string) {
        this.type = type;
   }
}

const person = new Person("Peter", "peter");

8.1. Automatic Creation of Properties

  • instead of writing this:

export class Ingredient {
    public name: string;
    public amount: number;

    constructor(name: string, amount: number) {
      this.name = name;
      this.amount = amount;
    }
  }
  • …​ this can be written with the same result:

export class Ingredient {

  constructor(public name: string, public amount: number) {
  }
}
  • properties will be automatically created and assigned with the parameters of the constructor

8.2. Inheritance

class Customer extends Person {

    constructor(username: string) {
        super("customer", username);        // super() necessary as first call in constructor!
        this.age = 42;                      // OK
        //this.type = "impossible!"           // error because "private"
    }
}

const customer = new Customer("myusername");

8.3. Getters and Setters

  • setters look like methods, but are not methods in TypeScript

class MyClass {

    private myAttribute: string;

    set setMyAttribute(value: string) {
        this.myAttribute = value;
    }

    get getMyAttribute() {
        return this.myAttribute;
    }
}

let myClass = new MyClass();
console.log(myClass.getMyAttribute);        // getMyAttribute is not a function!
myClass.setMyAttribute = "foo";             // setter also not a function!

8.4. Static Properties and Methods

class Helpers {
    static PI: number = 3.14;
    static doStuff(): void {}
}

Helpers.PI;
Helpers.doStuff();

8.5. Abstract Classes

  • can’t be instantiated directly, only by inheriting them

abstract class MyAbstractClass {
    // ...
}

8.6. Readonly Properties

class MyClass {
    constructor(public readonly myProperty: string) {}
}

let myClass = new MyClass();
myClass.myProperty = "x";       // error

9. Namespaces

  • only make sense for small projects; use modules for bigger projects!

namesapce MyNamespace1 {
    const MYCONST = "blubber";

    export function blubberize(content: string): string {
        return content + MYCONST;
    }
}

console.log(MyNamespace1.blubberize("my string is "));
  • important: functions in namespaces have to have an export to be used outside of the namespace

  • namespaces can extend over multiple files, just "declare" them in different files and import those files to the classes where they are used:

/// <reference path="myNamespace1.ts" />
/// <reference path="myFile2.ts" />

// normal code where you can use the new namespace:
console.log(MyNamespace1.blubberize("my string is "));
  • also possible to have namespaces in namespaces

10. Modules

  • classes, functions and attributes with export can be imported in other classes like this:

import { MYCONSTANT, myFunction } from "./myPath/myClassWithoutFileEnding";
  • native JavaScript doesn' support module, hence module loader required (not contained in this course)

  • also possible to use an alias:

import * as MyAlias from "./myPath/myClassWithoutFileEnding";
  • above is a relative path, which will be resolved in local project

  • absolute paths like this one will be resolved in node_modules folder:

import { Component } from "@angular/core";

11. Interfaces

  • no in-depth discussion about what object-oriented programming is here, but in short: interface = contract that a class promises to fulfill.

  • in this example, person is an object that has to have a name (and possibly other attributes):

function print(person: { name: string} ) {
    console.log(person.name);
}
  • to make contract reusable in multiple classes, extract as interface:

interface Person {
    name: string;
}

function print(person: Person ) {
    console.log(person.name);
}

11.1. Optional Properties

  • interfaces may have optional properties:

interface Person {
    name: string;
    age?: number;
}

const myPerson = { name: "bla" });

// OK, even with missing age
print(myPerson);

// Only OK with optional argument in interface! Without it: error.
print({ name: "bla", age: 42 });
  • Optional properties important because TypeScript checks direct arguments (passing objects literals directly to function) more strictly than when passing the same objects, but assigned to a constant first (like const myPerson).

  • if additional properties not known when defining the interface:

interface Person {
    name: string;
    age?: number;
    [ myTempName: string ]: any;
}
  • [ ] is not an array, but a special notation!

  • [ ] contains the name of the key

11.2. Methods in Interfaces

interface Person {
    name: string;
    age?: number;
    [ myTempName: string ]: any;

    print(name: string): void;
}

11.3. Interfaces and Classes

  • classes implement interfaces with keyword implements like in Java

11.4. Function Types

interface DoubleValueFunc {
    (number1: number, number2: number): number;
}

let myDoubleValuedFunction: DoubleValueFunc;
myDoubleValuedFunction = function(number1: number, number2: number) {
    return number1 + number2;
}
myDoubleValuedFunction(1,2);        // 3
  • "Whatever uses this interface must be a function of this type"

11.5. Interface Inheritance

interface Person {
    name: string;
}

interface ExtendedPerson extends Person {
    mail: string;
}

12. Generics

function myGenericFunction<T>(data: T) {
    return data;
}

myGenericFunction(42);                  // T = number
myGenericFunction<number>(42);          // T = number
myGenericFunction<number>("42");        // error
myGenericFunction("blubber");           // T = string
myGenericFunction<string>("blubber");   // T = string
myGenericFunction<string>(42);          // error

12.1. Built-in Generics

const bla: Array<number> = [1, 2];

bla.push(3);         // OK
bla.push("3");       // error

12.2. Generic Types

  • the following declares a constant echo which is of type <T>(data: T) ⇒ T which is assigned to the function myGenericFunction from above, which has this signature

const echo: <T>(data: T) => T = myGenericFunction;

echo("Bla");

12.3. Generic Classes

  • simple example:

class SimpleMath<T extends number> {

    baseValue: T;
    multiplyValue: T;

    calculate(): number {

        // the pluses cast this.baseValue and this.multiplyValue to numbers
        return +this.baseValue * +this.multiplyValue;
    }
}

const simpleMath = new SimpleMath<number>();

simpleMath.baseValue = 10;              // OK
simpleMath.baseValue = "10";            // error
simpleMath.multiplyValue = 20;          // OK

simpleMath.calculate();
  • example with more than one type:

// T should be of type number or string
class SimpleMath<T extends number | string> {

    baseValue: T;
    multiplyValue: T;

    calculate(): number {

        // the pluses cast this.baseValue and this.multiplyValue to numbers
        return +this.baseValue * +this.multiplyValue;
    }
}

const simpleMath = new SimpleMath<number>();

simpleMath.baseValue = 10;              // error
simpleMath.baseValue = "10";            // OK
simpleMath.multiplyValue = "20";        // OK

// This will yield the correct result because the strings above are casted automatically.
// However, passing stings like "blubber" will result in "NaN".
simpleMath.calculate();
  • example with multiple types:

  • example with more than one type:

class SimpleMath<T extends number | string, U extends number | string> {

    baseValue: T;
    multiplyValue: U;

    calculate(): number {

        // the pluses cast this.baseValue and this.multiplyValue to numbers
        return +this.baseValue * +this.multiplyValue;
    }
}

const simpleMath = new SimpleMath<number>();

simpleMath.baseValue = 10;              // OK
simpleMath.baseValue = "10";            // error because baseValue already determined to be a number
simpleMath.multiplyValue = "20";        // OK

simpleMath.calculate();

13. Decorators

  • functions attachable to classes, methods and properties

  • "meta-programming"

  • adding functionality without directly programming it

  • decorator = normal function, nothing special about it

  • every function becomes a decorator when it is attached to a class, method or property

  • multiple decorators can be attached

13.1. Class Decorators

  • has to have the Constructor-function of the class as the single parameter

function logged(constructorFn: Function) {
    console.log(constructorFn);     // will print "function Person() { }"
}

@logged
class Person {
    // implicit constructor omitted
}

13.2. Decorator Factories

  • the logged-function from above should have a boolean parameter to decide if something gets logged or not → not possible with the code above!

  • solution: decorator factory

  • "attach the result of the factory function"

function logged(constructorFn: Function) {
    console.log(constructorFn);     // will print "function Person() { }"
}

function logging(value: boolean) {
    return value ? logged : null;
}

@logging(false)
class Person {
}

@logging(true)
class Car {
}

13.3. Example for a useful Decorator

function printable(constructorFn: Function) {
    // the following adds a function "print" to every decorated class:
    constructorFn.prototype.print = function() {
        console.log(this);
    }
}

@printable
class Person {
    name = "Bob";
}

const person = new Person;

// has to be casted because of some bug;
// however: function print() is available because of decorator!
(<any>person).print();

13.4. Method Decorators

function editable(value: boolean) {
    return function(target: any, propName: string, descriptor: string) {
        descriptor.writable = value;
    }
}


class Project {
    projectName: string;

    constructor(projectName: string) {
        this.projectName = projectName;
    }

    @editable(false)
    calcBudget() {
        console.log(1000);
    }
}

const project = new Project("my project");
project.calcBudget();       // prints 1.000
project.calcBudget = function() {
    console.log(2000);
};                          // doesn't do anything because of decorator
project.calcBudget();       // prints 1.000

13.5. Property Decorators

  • TypeScript cannot access properties like methods (descriptor not available), hence different syntax:

function overwritable(value: boolean) {
    return function(target: any, propertyName: string): any {
        const newDescriptor: PropertyDescriptor = {
            writable: value;
        };
        return newDescriptor;
    }
}

class Project {
    @overwritable(false)
    projectName: string;        // will completely lock this value!
}

13.6. Parameter Decorators

function printInfo(target: any, methodName: string, paramIndex: number) {
    console.log(target);
    console.log(methodName);
    console.log(paramIndex);
}

class Course {
    name: string;

    constructor(name: string) {
        this.name = name;
    }

    printStudentNumbers(@printInfo printAll: boolean) {
        if(printAll) {
            console.log(1000);
        } else {
            console.log(2000);
        }
    }
}

const course = new Course();
course.printStudentNumbers(true);
You can’t perform that action at this time.