Typescript
Jump to navigation
Jump to search
Typescript
Typescript Training Materials
Copyright Notice
Copyright © 2004-2023 by NobleProg Limited All rights reserved.
This publication is protected by copyright, and permission must be obtained from the publisher prior to any prohibited reproduction, storage in a retrieval system, or transmission in any form or by any means, electronic, mechanical, photocopying, recording, or likewise.
Introduction
- Created by Microsoft
- Released under an open-source Apache 2.0 License (2004)
- Focused on making the development of JavaScript programs scale to many thousands of lines of code
- The large-scale JavaScript programming problem by offering:
- Better design-time tooling
- Compile-time checking
- Dynamic module loading at runtime
Intro con't
- Typed superset of JavaScript
- Is compiled to plain JavaScript
- Programs written in TypeScript are highly portable
- They can run on:
- Almost any machine - in web browsers
- Web servers
- And even in native applications on operating systems that expose a JavaScript API (WinJS, Web APIs)
Intro con't 1
- TypeScript language feature sources
- ECMAScript 5 specification forms the basis of TypeScript and supplies the largest number of features in the language
- The ECMAScript 6 specification adds modules for code organization and class-based object orientation
- Additional Features - includes items that are not planned to become part of the ECMAScript standard (generics and type annotations, etc)
- All of the TypeScript features can be converted into valid ECMAScript 5
Intro con't 2
- Can consume the existing libraries and frameworks written in JavaScript
- Angular, Backbone, Bootstrap, Durandal, jQuery, Knockout, Modernizr, PhoneGap, Prototype, Raphael, Underscore, React and many more
- Once TypeScript program has been compiled it can be consumed from any JavaScript code
- Similarity to JavaScript aids the debugging process
- The generated JavaScript correlates closely to the original TypeScript code
Intro con't 3
- Supported within:
- Visual Studio, Eclipse
- WebStorm, Sublime Text
- Vi, IntelliJ, Emacs
- Lots of other popular code editors
Installing TypeScript
- Command line
- Install with nodejs
npm install -g typescript
- Compile
tsc yourProgram.ts
- Install with nodejs
- Plugin to: Visual Studio Code, Sublime Text, Eclipse, Vim, etc
Preparing a TypeScript project
- File tsconfig.json
- Compiler Options
- Compiler Options in MSBuild (Visual Studio)
- Build Tools (Grunt, Gulp, Webpack, NuGet, etc)
- Example with VSC editor
Understanding Typing, Variables, and Functions
- All of the JavaScript standard control structures are immediately available within a TypeScript program:
- Control flows
- Data types
- Operators
- Subroutines
Transferring JavaScript into TypeScript
- All JavaScript is valid TypeScript
- Existing JavaScript code added to a TypeScript file - all of the statements will be valid
- There is a small number of exceptions
- The with statement
- Vendor specific extensions, such as Mozilla’s const keyword
- There is a small number of exceptions
- Difference between valid code and error-free code in TypeScript
- Although the code may work, the TypeScript compiler will warn about any potential problems it has detected
Example 1
// Not using with
var r = 6;
var a = Math.PI * r * r;
// Using with
var r = 6;
with (Math) {
var a = PI * r * r;
}
r = 'some string';
// Static type checking will shout: "Cannot convert 'string' to 'number'".
Variables
- Must follow the JavaScript naming rules
- First character of variable name must be one of the following:
- an uppercase letter, a lowercase letter, an underscore, a dollar sign
- a Unicode character - Uppercase letter (Lu), Lowercase letter (Ll), Title case letter (Lt), Modifier letter (Lm), Other letter (Lo), or Letter number (Nl)
- Subsequent characters follow the same rule but also allow:
- numeric digits
- a Unicode character — Non-spacing mark (Mn), Spacing combining mark (Mc), Decimal digit number (Nd), or Connector punctuation (Pc)
- the Unicode characters U+200C (Zero Width Non-Joiner) and U+200D (Zero Width Joiner)
- First character of variable name must be one of the following:
- Simple tester https://mothereff.in/js-variables
Variables con't
- Variables are functionally scoped
- Once declared at the top level of program, they are available in the global scope
- We should minimize the number of variables in the global scope to reduce naming collisions
- Variables declared in functions, modules, or classes are available in the context they are declared
- also in its nested contexts
Variables con't 1
- To create a global variable, declare it without the var keyword
- Often done inadvertently when the var is accidentally missed
- It is rarely done deliberately
- In a TypeScript program it will cause an error
- which prevents a whole category of hard to diagnose bugs in our code
Variables con't 2
- let vs old var
- with var
for (var i = 0; i < 10; i++) { setTimeout(function() { console.log(i); }, 100 * i); }
- fixed in the old way
for (var i = 0; i < 10; i++) { // capture the current state of 'i' // by invoking a function with its current value (function(i) { setTimeout(function() { console.log(i); }, 100 * i); })(i); }
- fixed with let
for (let i = 0; i < 10 ; i++) { setTimeout(function() { console.log(i); }, 100 * i); }
- with var
Variables con't 3
- const declaration
const maxTrainingsPerCart = 10;
- Value cannot be changed once it is bound
- Same scoping rules as let, but we can’t re-assign to them
- Internal state of a const variable is still modifiable
const trainingCartRestrictions = { name: "DefaultCart", maxTrainingsPerCart: 10 } // Error trainingCartRestrictions = { name: "MyCart", maxTrainingsPerCart: 10 }; // Still works trainingCartRestrictions.name = "ConsultancyCart"; trainingCartRestrictions.maxTrainingsPerCart--;
Const for objects, fixes
- Object.freeze()
const trainingCartRestrictions = Object.freeze({ name: "DefaultCart", maxTrainingsPerCart: 10 });
- as const
const trainingCartRestrictions = { name: "DefaultCart", maxTrainingsPerCart: 10 } as const;
Example 2
function subtrNrs(c, d) {
// missing 'var' keyword
subtr = c - d;
return subtr;
}
// An implicit global variable 'subtr', TypeScript will generate a "Could not find symbol" error
// To correct it: add the var keyword (makes variable locally scoped to 'subtrNrs'), or explicitly declare a variable in the global scope
Types
- TypeScript is optionally statically typed
- Types are checked automatically to prevent accidental assignments of invalid values
- It is possible to opt out of this by declaring dynamic variables
- Static type checking reduces errors caused by accidental misuse of types
- We can create types to replace primitive types to prevent parameter ordering errors
- Static typing allows development tools to provide intelligent auto-completion
Annotations
- TypeScript can infer types automatically
- the type of the variable is inferred from the value assigned
- Type annotation specifies the type
- Sometimes it isn’t able to determine the type
- We want to make a type explicit either for safety or readability
- For variable, the type annotation comes after the identifier and is preceded by a colon
Annotations Con't
The type used to specify an annotation can be:
- A primitive type, an array type, a function signature
- Any complex structure we want to represent
- Including the names of classes and interfaces
- To mark a variable’s type as dynamic, we can use the special any type
- No checks are made on dynamic types
Example 3
// primitive type annotation
var secondName: string = 'Robert';
var footLength: number = 26.50;
var isMarried: boolean = true;
// array type annotation
var languages: string[] = ['JavaScript', 'TypeScript', 'jQuery'];
// function annotation with parameter type annotation and return type annotation
var shoutHi: (name: string) => string;
// implementation of 'shoutHi' function
shoutHi = function (name: string) {
return 'Hi ' + name;
};
// object type annotation
var human: { secondName: string; footLength: number; };
// Implementation of a person object
human = {
secondName: 'Mat',
footLength: 28
};
Primitive Types
- Directly represent the underlying JavaScript types and follow the standards set for those types
- String - UTF-16 code units
- Boolean - true or false literals
- Number - double-precision 64-bit floating point value
- No special types to represent integers or other specific variations on a number
- any is exclusive to TypeScript and denotes a dynamic type
- used whenever TypeScript is unable to infer a type, or when we explicitly want to make a type dynamic
- equivalent to opting out of type checking for the life of the variable
Primitive Types Con't
Types that refer to the absence of values:
- undefined - value of a variable that has not been assigned a value
- null - represents an intentional absence of an object value
- method that searched an array of objects to find a match, it could return null to indicate that no match was found
- void - represents functions that do not return a value or as a type argument for a generic class or function
- never - no return, not even void ( ex. error, infinite loop, exception thrown )
Arrays
- Have precise typing for their contents
- We add square brackets after the type name
- Works for all types whether they are primitive or custom
- When we add an item to the array its type will be checked to ensure it is compatible
- When we access elements in the array, we will get quality auto-completion because the type of each item is known
Example 4
interface Movie {
title: string;
lengthMinutes: number;
}
// The array is typed using the Movie interface
var movies: Movie[] = [];
// Each item added to the array is checked for type compatibility
movies.push({
title: 'American History X',
lengthMinutes: 119,
production: 'USA' // example of structural typing
});
movies.push({
title: 'Sherlock Holmes',
lengthMinutes: 128,
});
movies.push({
title: 'Scent of a Woman',
lengthMinutes: 157
});
function compareMovieLengths(x: Movie, y: Movie) {
if (x.lengthMinutes > y.lengthMinutes) {
return -1;
}
if (x.lengthMinutes < y.lengthMinutes) {
return 1;
}
return 0;
}
// The array.sort method expects a comparer that accepts 2 Movies
var moviesOrderedLength = movies.sort(compareMovieLengths);
// Get the first element from the array, which is the longest
var longestMovie = moviesOrderedLength[0];
console.log(longestMovie.title); // Scent of a Woman
Exercise 4
Describe some Monuments with: - interface - array Compare their heights in a function, find the tallest one.
Enumerations
- Represent a collection of named elements
- To avoid littering our program with hard-coded values
- By default zero based
- We can specify the first value, so numbers will increment from the specified value
- We can also specify values for all identifiers, if needed
- The value passed when an identifier name is specified is the number that represents the identifier
- To get the identifier name from the enumeration, treat the enumeration like an array
Enumerations Con't
In TypeScript enumerations are open ended
- All declarations with the same name inside a common root will contribute toward a single type
- We can define an enumeration across multiple blocks
- Subsequent blocks after the first declaration must specify the numeric value to be used to continue the sequence
- Useful technique for extending code from third parties, in ambient declarations and from the standard library
Example 5
enum MusicKind {
Classic,
Rap,
Pop,
Rock,
Dance,
Funk
}
var kind = MusicKind.Funk; // 5
var kindName = MusicKind[kind]; // 'Funk'
// Enumeration split across multiple blocks
enum FootSize {
Small,
Medium
}
// ...
enum FootSize {
Large = 2,
XL,
XXL
}
Exercise 5
5.1. Describe types of Vehicles via enumeration. 5.2. Use enumeration to describe sizes of boxes (for example if we sell something in them): - split it across multiple blocks.
Assertions
Can override the type
- TypeScript determines that an assignment is invalid, but we know it's a special case
- We are guaranteeing that the assignment is valid
- The type assertion precedes a statement
- It overrides the type as far as the compiler is concerned
- There are still checks performed when we assert a type
- We can force a type assertion by adding an additional <any>
Example 6
interface Musician {
singles: number;
albums: number;
}
interface Rapper {
singles: number;
albums: number;
styles: number;
}
var eminem: Musician = {
singles: 120,
albums: 80,
styles: 4
}
// Errors: Cannot convert Musician to Rapper
var rapper: Rapper = eminem;
// Works
var rapper: Rapper = <Rapper>eminem;
// Forced type assertions
var rapperName: string = 'Rapper Eminem';
// Error: Cannot convert 'string' to 'number'
var singles: number = <number> rapperName;
// Works
var singles: number = <number> <any> rapperName;
Exercise 6
Prepare two interfaces: House and Mansion. The second should have one more property than the first one. Declare new variable with type from House interface - but provide also that additional parameter(property) from the Mansion interface - fix it with type assertion Finally try to force the type assertion.
Operators
Some JavaScript operators have special significance within TypeScript
- because of type restrictions
- they affect types
Increment, Decrement
- Increment (++) and decrement (--) operators can only be applied to variables of type
- any, number, or enum
- The operator usually works on variables with the any type, as no type checking is performed on these variables
- When incrementing or decrementing an enumeration, the number representation is updated
- Incrementing results in the next element in the enumeration
- Decrementing results in the previous element in the enumeration
- We can increase and decrease the value beyond the bounds of the enumeration
Example 7
enum FootSize {
Small,
Medium,
Large,
XL,
XXL
}
var fsize = FootSize.Small;
++fsize;
console.log(FootSize[fsize]); // Medium
var fsize = FootSize.XL;
--fsize;
console.log(FootSize[fsize]); // Large
var fsize = FootSize.XXL;
++fsize;
console.log(FootSize[fsize]); // undefined
Binary Operators
- Binary operators: - * / % << >> >>> & ^ |
- Designed to work with two numbers
- In TypeScript, it is valid to use the operators with variables of type number or any
- Where we are using a variable with the any type, we should ensure it contains a number
- The result of an operation in this list is always a number
Binary Operators Con't
- The plus (+) operator can be
- Mathematical addition operator
- Concatenation operator (glue strings)
- This will be caught in a TypeScript program if we
- try to assign a string to a variable of the number type
- try to return a string for a function that is annotated to return a number
Logical Operators
- NOT - ! inverts, !! converts to BOOLEAN
- AND - &&
- OR - ||
- Short-Circuit Evaluation
- Conditional Operator - isOK ? 'Y' : 'N'
Type Operators
- Can assist us when working with objects in JavaScript
- Relevant to working with classes - typeof, instanceof, in, delete
Functions
- In TypeScript most functions are actually written as methods that belong to a class
- Functions are improved by a number of TypeScript language features
- With functions there are a number of places that can be annotated with type information
- Each parameter can have a different type
- When the function is called, the type of each argument passed to the function is checked
- The types are also known within the function
- Sensible auto-completion suggestions and type checking inside the function body
Functions Con't
- Additional type annotation outside of the parentheses indicates the return type
- We can use the void type to indicate that the function does not return a value
- Prevent code inside the function from returning a value
- Stop calling code from assigning the result of the function to a variable
- It is possible to specify all of the types used in a function explicitly
- We should rather rely on type inference
- For functions it is worth leaving out the return type unless the function returns no value
Example 8
function countAvr(x: number, y: number, z: number): string {
var together = x + y + z;
var avr = together / 3;
return 'The average is ' + avr;
}
var res = countAvr(3, 2, 10); // 'The average is 5'
Parameters
- In JavaScript
- we can call a function without any arguments (even if specified in it's declaration)
- we can pass more arguments than the function requires
- In TypeScript the compiler checks each call
- Warns if the arguments fail to match the required parameters in number or type
- Thoroughly checked, we need to annotate optional parameters
- Inform the compiler that it is acceptable for an argument to be omitted by calling code
- To make a parameter optional, suffix the identifier with a question mark
- Optional parameters must be located after any required parameters
- We must check the value to see if it has been initialized (typeof check)
Example 9
function countAvr(x: number, y: number, z?: number): string {
var tog = x;
var counter = 1;
tog += y;
counter++;
if (typeof z !== 'undefined') {
tog += z;
counter++;
}
var avr = tog / counter;
return 'The average is ' + avr;
}
var res = countAvr(2, 8); // 'The average is 5'
Parameters Con't
- Default parameter allows the argument to be omitted by calling code
- In cases where the argument is not passed the default value will be used instead
- To supply a default value for a parameter, assign a value in the function declaration
- Checks only appear in the output
- Keeps the TypeScript code listing short and succinct
Example 10
function conc(i: string[], sep = ',', b = 0, e = i.length) {
var resu = '';
for (var k = b; k < e; k++) {
resu += i[k];
if (k < (e - 1)) {
resu += sep;
}
}
return resu;
}
var items = ['D', 'E', 'F'];
// 'D,E,F'
var res = conc(items);
// 'E-F'
var partRes = conc(items, '-', 1);
Parameters Con't 1
- Rest parameter allows calling code to specify zero or more arguments of the specified type
- Must follow these rules
- Only one rest parameter is allowed
- Must appear last in the parameter list
- Must be an array type
- To declare a rest parameter, prefix the identifier with three periods
- ensure that the type annotation is an array type
Example 11
function countAvr(...avr: number[]): string {
var t = 0;
var c = 0;
for (var k = 0; k < avr.length; k++) {
t += avr[k];
c++;
}
var av = t / c;
return 'The average is ' + av;
}
var res = countAvr(1, 6, 8, 9, 11); // 'The average is 7'
Overloads
- Separate, well-named functions that make their different intentions explicit
- In TypeScript all decorate a single implementation
- The actual signature of the function appears last and is hidden by the overloads
- This final signature is called an implementation signature
- Must define parameters and a return value, that are compatible with all preceding signatures
- Return types for each overload can be different
- Parameter lists can differ in types and in number of arguments
- This final signature is called an implementation signature
- If an overload specifies fewer parameters than the implementation signature
- the implementation signature would have to make the extra parameters
- optional, default, or rest parameters
- the implementation signature would have to make the extra parameters
Overloads Con't
- When we call a function that has overloads defined
- the compiler constructs a list of signatures
- attempts to determine the signature that matches the function call
- If there are no matching signatures the call results in an error
- If one or more signature matches, the earliest of the matching signatures determines the return type
- In the order they appear in the file
- Overloads allow a single function to be used in multiple cases
Example 12
function countAvr(x: string, y: string, z: string): string;
function countAvr(x: number, y: number, z: number): string;
// implementation signature
function countAvr(x: any, y: any, z: any): string {
var t = parseInt(x, 10) + parseInt(y, 10) + parseInt(z, 10);
var av = t / 3;
return 'The average is ' + av;
}
var res = countAvr(2, 4, 9); // 5
Specialized Overload Signatures
- Specialized overload signatures
- The ability in TypeScript to create overloads based on string constants
- The overloads are based on the string value of an argument
- A single implementation of a function can be re-used
- In many cases without requiring the calling code to convert the types
- The nonspecialized signature returns a superclass
- With each overload returning a more specialized subclass that inherits the superclass
- An example: (DOM) method getElementsByTagName
- We get back an appropriately typed NodeList depending on the HTML tag name we pass to the function
Specialized Overload Signatures Con't
- Rules
- There must be at least one nonspecialized signature
- Each specialized signature must return a subtype of a nonspecialized signature
- The implementation signature must be compatible with all signatures
Example 13
class HandlerFactory {
getHandler(type: 'Random'): RandomHandler;
getHandler(type: 'Reversed'): ReversedHandler;
getHandler(type: string): Handler; // non-specialized signature
getHandler(type: string): Handler { // implementation signature
switch (type) {
case 'Random':
return new RandomHandler();
case 'Reversed':
return new ReversedHandler();
default:
return new Handler();
}
}
}
Arrow Functions
- Shorthand syntax for defining a function
- Allow to leave out the function and return keywords
- We can also use it to preserve the lexical scope of the this keyword
- Useful when working with callbacks or events (when it's easy to lose the current scope)
- Behind the scenes - just before the arrow function is defined - TS creates _this and sets its value to the current value of this
- It also substitutes any usages of this within the function with _this, so the statement now reads _this.property in the JavaScript output
- The use of the _this variable inside the function creates a closure around the variable, which preserves its context along with the function
- This pattern is useful if we ever need both the original meaning of this as well as the functionally scoped meaning of this (events, etc)
Example 14
// Arrow functions
var addNrs = (x: number, y: number) => x + y;
var addNrs = (x: number, y: number) => {
return x + y;
}
var addNrs = function (x: number, y: number) {
return x + y;
}
// Wrapping an object in parentheses
var createName = (f: string, l: string) => ({first: f, last: l});
// Preserving scope with arrow syntax
var ScopeLosingEx = {
text: "Property from lexical scope",
run: function () {
// Here we have the usual JS way
setTimeout( function() {
alert(this.text);
}, 1000);
}
};
// alerts undefined
ScopeLosingEx.run();
var ScopePreservingEx = {
text: "Property from lexical scope",
run: function () {
// Here below goes the main change in code
setTimeout( () => {
alert(this.text);
}, 1000);
}
};
// alerts "Property from lexical scope"
ScopePreservingEx.run();
Working with Classes and Interfaces
Interfaces
- Can be used for several purposes
- As an abstract type that can be implemented by concrete classes
- To define any structure in our TypeScript program
- The building blocks for defining operations
- That are available in third-party libraries and frameworks that are not written in TypeScript
- Declared with the interface keyword
- Contain a series of annotations to describe the contract represented by them
- Can describe properties, functions, constructors and indexers
- To describe our own classes, we won’t need to define constructors or indexers
- They are included to help us describe external code with structures that may not be analogous to classes
Example 15
// Interfaces examples
interface CourseVenue {
// Properties
city: string;
country: string;
}
interface CourseParticipant {
// Properties
name: string;
}
interface CourseEvent {
// Constructor
new() : CourseEvent;
// Properties
currentLocation: CourseVenue;
// Methods
bookVenue(venue: CourseVenue);
addDelegate(delegate: CourseParticipant);
removeDelegate(delegate: CourseParticipant);
}
Exercise 15
Create a set of interfaces to describe a vehicle, passengers, location, and destination. Declare properties and methods using type annotations. Declare constructors using the ''new'' keyword.
Interfaces Con't
- They do not result in any compiled JavaScript code (due to type erasure)
- Used at design time to provide autocompletion
- Used at compile time to provide type checking
- Open like enumerations - all declarations with a common root are merged into a single interface
- This means we must ensure that the combined interface is valid
- We can’t declare the same property in multiple blocks of the same interface ("duplicate identifier" error)
- We can’t define the same method (although we can add overloads to an existing one)
- Declaring an interface in several blocks
- Not a particularly valuable feature for own program
- Very good to extend built-in definitions or external code
Example 16
// Built-in NodeList interface
/*
interface NodeList {
length: number;
item(index: number): Node;
[index: number]: Node;
}
*/
// An additional interface block extends the built-in NodeList interface
// to add an onclick property that is not available natively.
// The implementation isn’t included in this example - it may be a new web standard
// or a JavaScript library that adds the additional functionality.
// As far as the compiler is concerned, the interface that is defined in the standard library
// and the interface that is defined in our TypeScript file are one interface
// Extending the NodeList interface example
interface NodeList {
// Function arrow shortcut (event case)
onclick: (event: MouseEvent) => any;
}
var nodeList = document.getElementsByTagName('div');
nodeList.onclick = function (event: MouseEvent) {
alert('Clicked');
};
Interfaces Con't 1
- Can be used to describe
- A contract we intend to implement in a class
- Any structure we can conceive in our program (functions, variables, objects, or combinations of them)
- When a method accepts an options object as a parameter (jQuery, other similar frameworks)
- An interface can be used to provide autocompletion for the complex object argument
- Can inherit from a class in the same way a subclass can inherit from a superclass
- The interface inherits all of the members of the class, but without any implementation
- Anything added to the class will also be added to the interface
- Useful when used in conjunction with generics (covered later here)
Classes
- The fundamental structural elements
- Serve to organize our program
Constructors
- All classes in TS have a constructor, whether we specify one or not
- If we leave out the constructor, the compiler will automatically add one
- Class that doesn’t inherit from another class
- The automatic constructor will be parameterless and will initialize any class properties
- Class extends another class
- The automatic constructor will match the superclass signature
- And will pass arguments to the superclass (before initializing any of its own properties)
Example 17
// Constructors example
class Training {
constructor(private category: string, private title: string, private noOfDays: number) {
}
buy() {
console.log('Buying this ' + this.noOfDays + ' day(s) ' + this.title + ' course from category ' + this.category);
}
}
// Constructor parameters are mapped to member variables.
// If we prefix a constructor parameter with an access modifier (ie. private),
// it will automatically be mapped for us.
// We can refer to these constructor parameters as if they were declared
// as properties on the class, for example this.title, can be used anywhere within the Training class
// to obtain the Training title on that instance
class BuyTraining {
constructor(private trainings: Training[]) {
}
buy() {
var training = this.chooseTraining();
training.buy();
}
private chooseTraining() {
// Decision can come for example from the form in the browser, another webservice, db, etc
var whichTraining = 2;
return this.trainings[whichTraining];
}
}
var trainings = [
new Training('Drupal', 'Drupal 8 for Developers', 2),
new Training('Angular', 'Angular 2 Fundamentals', 3),
new Training('Nodejs', 'Developing web applications with the MEAN stack', 5),
new Training('SQL', 'T-SQL basics', 2),
new Training('Management', 'BPMN for code architects', 3)
];
var choice = new BuyTraining(trainings);
choice.buy();
Exercise 17
Create structure about songs and make jukebox, which will randomly get the song from the list of songs.
- Math.floor(Math.random() * songCount);
Access Modifiers
- Can be used to change the visibility of properties and methods within a class
- By default, properties and methods are public (no need to use public keyword)
- If we want constructor parameters to be mapped to public properties automatically
- They have to be prefixed with the public keyword
- private keyword hides a property or method
- This restricts the visibility to within the class only
- The member won’t appear in auto-completion lists outside of the class
- Any external access will result in a compiler error
- A class member marked as private, can’t even be seen by subclasses
- If we need to access a property or method from a subclass
- We can made it public
- We can use protected keyword
- readonly keyword
Properties and Methods
- Instance properties are typically declared before the constructor in a TypeScript class
- A property definition: an optional access modifier, the identifier, and a type annotation
public name: string;
- with a value:
public name: string = 'Jane';
- When our program is compiled, the property initializers are moved into the constructor
- Instance properties can be accessed from within the class using the this keyword
- If the property is public it can be accessed using the instance name
- Static properties are defined with the static keyword between the access modifier and the identifier
- Static properties are accessed using the class name
Methods
- Methods are defined like functions, but without the function keyword
- Can be annotated with parameters and return value type
- Can be prefixed with an access modifier to control its visibility (public by default)
- Can be accessed from within the class via this keyword
- If public, can be accessed outside of the class using the instance name
- Static members can be called even when no instance of the class has been created
- Only a single instance of each static member exists in our program
- All static members are accessed via the class name and not an instance name
- Static members have no access to nonstatic properties or methods
Example 18
// Properties and methods
class CartWithTrainings {
private trainings: Training[] = [];
static maxTraining: number = 10;
constructor(public cartId: string) {
}
addTraining(training: Training) {
if (this.trainings.length >= CartWithTrainings.maxTraining) {
throw new Error('To many courses in your Cart.');
}
this.trainings.push(training);
}
}
// Creating a new instance
var coursesCart = new CartWithTrainings('Cart1');
// Accessing a public instance property
var nameCart = coursesCart.cartId;
// Calling a public instance method
coursesCart.addTraining(new Training('GIT', 'Git for Users', 1));
// Accessing a public static property
var maxTrainings = CartWithTrainings.maxTraining;
Exercise 18
Make a Playlist of songs (reuse code from exercise 17) - declare class for playlist -- use private and static properties - prepare 'addSong' method - create new instance, access and call: -- playlist, its public instance property, public instance method, public static property
Property getters and setters
- TS supports property getters and setters, as long as we are targeting ECMAScript 5 or above
- The syntax is identical to method signatures
- Except they are prefixed by either the get or set keyword
- Allow to wrap property access with a method
- While preserving the appearance of a simple property to the calling code
Example 19
// Property getters and setters example
interface Invoice {
id: string;
clientId: number;
}
class FranchiseeAccountancyTool {
private _invoice;
constructor(public accToolKey: string, public taxRate: string) {
}
get invoice() {
return this._invoice;
}
set invoice(inv: Invoice) {
this._invoice = inv;
}
}
var consultancyInv = { id: 'INV_20_12072015', clientId: 234 };
var accToolInstance = new FranchiseeAccountancyTool('cnaj837tjdhsu#jd9_fd8', '201');
accToolInstance.invoice = consultancyInv;
Exercise 19
Create an interface describing stock item - allow to get and set it within warehouse location (make it as a class) - make new slot in warehouse and put there new stock item object
Heritage
- A class can implement an interface using the implements keyword
- a class can inherit from another class using the extends keyword
Example 20
// Class heritage example
interface Audio {
play(): any;
}
class Song implements Audio {
constructor(private artist: string, private title: string) {
}
play(): void {
console.log('Playing ' + this.title + ' by ' + this.artist);
}
static Comparer(a: Song, b: Song) {
if (a.title === b.title) {
return 0;
}
return a.title > b.title ? 1 : -1;
}
}
class Playlist {
constructor(public songs: Audio[]) {
}
play() {
var song = this.songs.pop();
song.play();
}
sort() {
this.songs.sort(Song.Comparer);
}
}
class RepeatingPlaylist extends Playlist {
private songIndex = 0;
constructor(songs: Song[]) {
super(songs);
}
play() {
this.songs[this.songIndex].play;
this.songIndex++;
if (this.songIndex >= this.songs.length) {
this.songIndex = 0;
}
}
}
Exercise 20
Choose one of your business processes and map it into 2-3 classes - provide an interface - at least one class should use provided interface - make one class inherit from another
Organizing your code with Namespaces
- Namespaces (previously Internal modules)
- Named JavaScript objects in the global namespace
- Simple construct to use
- Can span multiple files
- Can be concatenated (using --outFile).
- Good way to structure our code in a Web Application
- With all dependencies included as <script> tags in our HTML page
- It can be hard to identify component dependencies, especially in a large application
Example 21
namespace Shipping {
// Available as Shipping.Ship
export interface Ship {
name: string;
port: string;
displacement: number;
}
// Available as Shipping.Ferry
export class Ferry implements Ship {
constructor(
public name: string,
public port: string,
public displacement: number) {
}
}
// Only available inside of the Shipping module
let defaultDisplacement = 4000;
class PrivateShip implements Ship {
constructor(
public name: string,
public port: string,
public displacement: number = defaultDisplacement) {
}
}
}
let ferry = new Shipping.Ferry('Assurance', 'London', 3220);
Exercise 21
1. Discuss if it would make sense to rewrite solutions from ex17 and ex18 as namespace - if yes, try to do it
2. Improve the code below with namespace - add another validator for numbers(zip code) and related regexp pattern (/^[0-9]+$/) - export validators - hide regexps
interface StringValidator {
isAcceptable(s: string): boolean;
}
let lettersRegexp = /^[A-Za-z]+$/;
class LettersOnlyValidator implements StringValidator {
isAcceptable(s: string) {
return lettersRegexp.test(s);
}
}
// Some samples to try
let strings = ["Hello", "98052", "101"];
// Validators to use
let validators: { [s: string]: StringValidator; } = {};
validators["Letters only"] = new LettersOnlyValidator();
// Show whether each string passed each validator
for (let s of strings) {
for (let name in validators) {
let isMatch = validators[name].isAcceptable(s);
console.log(`'${ s }' ${ isMatch ? "matches" : "does not match" } '${ name }'.`);
}
}
Exercise 22
Split previous code into a couple of files: - val.ts, lettersVal.ts, zipVal.ts, test.ts - provide necessary references in the 'test.ts' -- for example "/// <reference path="val.ts" />" - compile it as one output file (using --outFile)
Reusing code through Modules
- Modules
- Can contain both code and declarations
- Declare their dependencies
- Have a dependency on a module loader (for example CommonJs/Require.js).
- Might not be optimal for a small JS application
- For larger applications
- Long term modularity and maintainability benefits
- Better code reuse
- Stronger isolation
- Two different modules will never contribute names to the same scope
- Better tooling support for bundling
- Default and recommended approach for nodejs apps
- Third party libraries, extensions, etc
- No longer needed - npm packages descriptions have it now
Example 23
// File 'greet.ts'
function greet(name: string): void {
console.log('Hello ' + name);
}
//// nodejs way
export = greet;
//// Example with an interface - modern js way
// export { nameOfTheInterface }
* =========================================== *
// File 'hello.ts'
//// Without 'ts'
// var hello = require('./scripts/greet');
//// With 'ts'
import hello = require('./scripts/greet'); // with 'ts'
// Example with interface
// import { nameOfTheInterface } from "./scripts/greet"
hello('Mark'); // instead of hello.greet('Mark');
Exercise 23
Rewrite code from ex22 into modules. (hints: - from the dependency: export { StringValidator } - in the main file: import { StringValidator } from "./val"; )
Generics
Type variable, special kind of variable
- works on types rather than values
- allows to create reusable components
Example 24
function whoAmI<G>(param: G): G {
return param;
}
let resu = whoAmI<string>("I am String"); // type of output will be 'string'
// type argument inference
let resu1 = whoAmI("I also am String"); // type of output will be 'string'
Exercise 24
- Test it with number, array, object
Example 25
Call signature of an object literal type
function whoAmI<U>(param: U): U {
return param;
}
let amIme: {<U>(arg: U): U} = whoAmI;
Generic interface
interface GenericWhoAmIFn {
<B>(param: B): B;
}
function whoAmI<B>(param: B): B {
return param;
}
let amIme: GenericWhoAmIFn = whoAmI;
console.log( amIme("Of course not, you're in the Matrix, Mate..") );
Generic class
class GenericInvoice<C> {
invoiceOwner: C;
send: (invID: C, invType: C) => C;
}
let myInvoice = new GenericInvoice<string>();
myInvoice.invoiceOwner = 'Judy';
myInvoice.send =
function(invID, invType) {
let invRemarks = invType + " invoice with number " + invID + " has been issued";
return invRemarks;
};
console.log( myInvoice.send("12-23012018", "ProForma") + " by " + myInvoice.invoiceOwner );
Exercise 25
- Reuse class from Example 25 with array type
- Create your custom generic class, use function from Example 8
Compiling, testing and running TypeScript
- Compiling
- Command line
tsc -p <project_path> tsc file_name.ts
- Command line
- Testing
- Possible in plain TypeScript code
- Testing frameworks written in JavaScript
- Jasmine
- Mocha
- QUnit
Debugging TypeScript
- Browser debuggers (Chrome inspector, Firebug, additional extensions, etc)
- node --inspect dist
- IDEs specific debuggers (built in or plugins)
Launching your application
- At the beginning usually it's a good idea to use "seed" (start-up) projects
- Full and pre-configured environment, easy to extend and learn from it
- Good examples of code - application and tests, ready to be reused
- TS propositions
Exercise
Install one of these quick-starters - https://github.com/Microsoft/TypeScript-React-Starter#typescript-react-starter - https://angular.io/guide/quickstart - https://github.com/Microsoft/TypeScript-Node-Starter#typescript-node-starter Create simple TODO list with MVC (Model View Controller) approach.
Closing remarks
- All JavaScript is technically valid TypeScript
- Primitive types are closely linked to JavaScript primitive types
- Types are inferred in TypeScript, but you can supply annotations to make types explicit or deal with cases the compiler can’t handle
- Interfaces can be used to describe complicated structures, to make type annotations shorter
- All TypeScript arrays are generic
- There are special cases where type coercion applies, but in most cases type checking will generate errors for invalid use of types
- You can add optional, default, and rest parameters to functions and methods
- Arrow functions provide a short syntax for declaring functions, but can also be used to preserve the lexical scope
- Enumerations, interfaces, and modules are open, so multiple declarations that have the same name in the same common root will result in a single definition
- Classes bring structure to your TypeScript program and make it possible to use common design patterns
- Modules work as namespaces and external modules can help with module loading
- You can obtain type information at runtime, but this should be used responsibly
- What is coercion?
- Type coercion means that when the operands of an operator are different types, one of them will be converted to an "equivalent" value of the other operand's type. For instance, if you do:
- boolean == integer
- the boolean operand will be converted to an integer: false becomes 0, true becomes 1. Then the two values are compared.