- TypeScript
js
uses dynamic
types resolved during runtime while ts
uses static
types set during development.
any
- Var can take any data type.Unknown
- When initial val of the var is unknown - safer thatany
.number
,string
,boolean
.
Concatenate 2 types using or
like string | number
.
-
Object This can be written in 2 syntaxes:
// Long syntax => Done behind the scenes anyways const obj: { name: string; age: number; } = { name: 'Jane Doe', age: 20, };
//Normal objects const obj = { name: 'Jane Doe', age: 20, };
-
Arrays Can be of a particular type. Syntax is like: with
dataTypeOfArray[]
// Array types let games: string[]; games = ['hockey'];
For array with mutiple data types:
let multiple: (string | number)[];
The pipe
|
is aunion
operator. -
Tuples Special arrays with an exact number of elements in an order with optional types given.
let tuple: [number, string]; tuple = [3, ''];
-
Enums Are like objects but only identifiers are written. By default, the first element has the value
0
and so on based on their index. This index can also be set to a desired value or even string:enum Role { ADMIN, USER, REGULAR, } console.log(Role.ADMIN); // Prints 1 // Dynamic setting
enum Role { ADMIN = 'ADMIN', USER = 9, REGULAR, }
Its convention to use
CamelCase
forenum
name and values as caps. -
Union Types Vs Literal Types Union types points to a var accepting more than 1 inbuilt data type. Literal types are more specific values a var can hold.
let literalType: 'text' | 'otheValue';
This therefore ensures that the var
literalType
can only hold one of the 2 specified strings.
When managing types, all types that a var can take can be stored in a single type
variable and any other var accepting the same types can refer to this alias and the types held are asigned to it. They are declared using the type
keyword:
type NumOrString = number | string;
let aliasUser: NumOrString;
aliasUser = 3;
-
Function Return type To begin, a functions return type can be specified. If it doesn't return anything, then the
void
return type is used. Thou only specify a funcs return type if you have a reason to:function someFunction(): number { return 3; } function someFunction(): void { return; }
-
Function Type These are for vars that hold functions as their values:
let functionVar: Function; functionVar = someFunction;
Having more constrol on the type of function a var can store requires specifying what the fuction the var holds should take as parameter and should return:
let specificFunctionVar: (a: number, b: string) => number;
specificFunctionVar = someFunction;
This will therefore accept the specified func type or one that takes no argument but returns a number
.
-
Never Return Type This is mostly for error functions that are used to throw an error and therefore stop script execution meaning nothig is returned from them:
function appError(message: string, code: number): never { throw { message, errorCode: code }; }
The same can also apply to functions with an infinite loop.
Basic normal structure:
class Book {
constructor(name: string) {
this.name = name;
}
name: string;
information(this: Book) {
console.log(`This book is named ${this.name}`);
}
}
const book = new Book('Awesome book');
console.log(book);
book.information();
Instead of declaring the attributes of a class separate then initialize them in the constructor, both of these activities can be done directly in the constructor:
class Car {
constructor(private id: number, public name: string) {}
}
class C {
public readonly name: string;
}
class Book {
constructor(public id: number, public name: string) {}
}
class DSABook extends Book {
constructor(public id: number, public topic: string) {
// Call Book constructor with a name for this class and the passed in id
super(id, 'DSA');
}
printBook() {
console.log(`Book id ${this.id} named ${this.name}`);
}
}
Start with keywords get
or set
. A getter must return something and a setter accesses a value as param and sets a class attribute. Both when called are not executed as functions.
class Car {
constructor(public name: string) {}
set carName(name: string) {
this.name = name;
}
get carName() {
return this.name;
}
}
const car = new Car('Revo');
car.carName = 'Toyota'; // Setter
console.log(car.carName); // Getter
Are properties directy associated with the class itself and not its instances. Thus a statement like this.statFunc()
will not work.
class Test {
static identifier = 'Value';
static statFunc() {}
}
Test.identifier;
Test.statFunc();
An abstract class cannot be instantiated. It's used to create a vlueprint for all methods and/or properties that the child classes must implement in their structure. Therefore abstract attributes of this class should not be implemented and the abstract methods are not implemented.
abstract class AbstractClass {
abstract name: string;
abstract informaiton(): void;
}
An abstract method or prop must be in an abstract class.
Involves an aspect of any particular class having only one instance. The instance is created using a static method and once created, no other can be created. Achieved using static attributes and methods and use of a private constructor
.
class Singleton {
private static instance: Singleton;
private constructor(public name: string) {}
static getInstance() {
if (this.instance) {
// this in statics refers to the class itself
return this.instance;
}
return new Singleton('Default only instance');
}
}
const instance = Singleton.getInstance();
console.log(instance);
Therefore calling the constructor ie new SingletonClass()
won't work as the constructor is private.
Is a data type thingy that allows decribing the structure of an object. This may include methods & properties it may have.
interface Music {
trackName: string;
playPeriod: number;
play(): void;
}
let iWantItThatWay: Music;
iWantItThatWay = {
trackName: 'I want it that way',
playPeriod: 3.24,
play() {
console.log(`Playing ${this.trackName}`);
},
};
A class can implement more than 1 interface, separated by commas unlike in inheritance where it can only inherit from 1 class. Interfaces can provide the attributes and functions that a class must implement. It differs from abstract
classes as they can never have any instantiation or implementation of some or any of its internals.
interface Playable {
game: string;
playing(person: string): void;
}
class Player implements Playable {
constructor(public game: string) {}
playing(person: string): void {
console.log(`${person} is playing ${this.game}`);
}
}
let person1: Playable;
// or
// let person1: Player => Both will work
person1 = new Player('Soccer');
person1.playing('Hassan');
The internals of an interface
can have the modifier readonly
implemented which will be effective in all implemented classes. It thou cannot have modifiers public
, private
& the likes.
An interface can extend another interface - like in inheritance
- and this can work for extension of more than one interface unlike in classes where they can only extend exactly 1 other class.
interface InterFace1 {
name: string;
}
interface InterFace2 {
age: number;
}
interface InterFace3 extends InterFace1, InterFace2 {
otherName: string;
}
Interfaces can be an alternative to the type
for function type structure definitions. It works as:
interface funcType {
// Params and return type
(num: number, num2: number): number;
}
let addFunc: funcType;
addFunc = (a: number, b: number) => a + b;
Both classes and interfaces can have optional attributes & methods. Implemented with a ?
after their name. One can be optional in the interface but implemented as a must in the implementing class.
class optional {
name?: string;
method?(): void;
}
These are special types created from other types. If the type is an object, then all unique properties will be inherited, and if its a type of data types like string | number
, then the common data type that appears in both the intersected types will be used by the inheriter. This similar logic can be implemented using interfaces
instead of types
and still work the same:
type master = {
skills: string;
disciples: string[];
};
type disciple = {
skill: string;
};
type apprentice = master & disciple;
const appr: apprentice = {
skills: 'Some skills',
disciples: ['yen', 'chen'],
skill: 'wing-chun',
};
In cases eg the above where a type is made of 1+ other types, there might exist a clash. Examining the code below:
const strNum: string | number;
When using the var for an operation, eg multiplication, it has to be checked first if it is a number or else functionality may fail. This is still the case when using the object types
but as in this case a simple typeof
operator can be used, for objects is different as the typeof
will always return an object.
In the example above, if the intersection was an or
, then an apprentice may sometimes not have the disciples
property & thus the same must be checked. One way mignt be:
type sampleAppr = master | disciple;
const displaySampleAppr = (appren: sampleAppr) => {
if ('disciples' in appren) console.log(appren.disciples);
};
FOr the above code, if the types master
and apprentice
were classes instead of types, then the checking of existance of a property in the guard clause can be done using the inbuilt instanceof
operator for classes:
const displaySampleAppr = (appren: sampleAppr) => {
if (appren instanceof master) console.log(appren.disciples);
};
The same won't work for interfaces as there are no interfaces in js
, that is after ts
compilation.
As the above check cannot work for interfaces, this can be used to check for such. A literal type
can be used in each of the related interface and using this type and a switch statement, we'll be ble to determine the type of the instance the this
is.
interface Dragon {
type: 'dragon';
flySpeed: number;
}
interface Shark {
type: 'shark';
swimSpeed: number;
}
type Creature = Dragon | Shark;
const creatureMove = (ani: Creature) => {
let speed;
switch (ani.type) {
case 'dragon':
speed = ani.flySpeed;
break;
case 'shark':
speed = ani.swimSpeed;
break;
}
};
When accessing dom elements using selectors like id
and classNames
, ts doesn't know the type of element that is selected and this accesing the operations that can be done on it eg .value
property of input
elements, may yield an error as the property may not exist in other html elements. Thats where type casting comes in:
These 2 syntax can be used:
const inputEl = document.getElementById('name')! as HTMLInputElement;
const btn = <HTMLButtonElement>document.getElementById('btn');
Useful when creating like an index but not sure on what exactly will be included in it. Example when creating errors for different input elements and therefore the type of error & error message shown is dependent on the user input:
interface ErrorInterface {
[prop: string]: string;
}
const emailNameError: ErrorInterface = {
email: 'Input a valid email',
name: 'Invalid name input',
};
The prop data type refers to the key
type and the other is the value
type. Thou with string as a key type, a number can be used as key.
Using an example where a function takes in params that can either be a string or number and return a value based on that, a further configuration can be performed on the same function to give ts an extra clue on what the output will be based on the iput type without this, ts will shouw the return type as either a string or a number, ie the combined data type.
type strNum = string | number;
// Overloads
function add(a: number, b: number): number;
function add(a: string, b: string): string;
// Actual function
function add(a: strNum, b: strNum) {
if (typeof a === 'string' || typeof b === 'string') {
return a.toString() + b.toString();
}
return a + b;
}
add('a', 'b');
add(1, 2);
Refer to them as type thingys that give a general clue of what a complex data type contains or will yield in the end and such. Examples of inbuilt generics
are arrays
& Promises
. They are specifically used to restrict a certain item/object to a specific type of data it works with.
const arr: Array<number> = [1, 2, 3];
arr[0].toFixed(2);
const promise: Promise<string> = new Promise((resolve, reject) => {
setTimeout(() => {
resolve('Promise resolved!');
}, 2000);
});
promise.then((data) => data.split(' '));
In this example, the array is defined as will be holding numbers and therefore you get ts support when accessing and working with the array elements. Same is true for the promise which is expected to return a string & therefore during thr .then()
, ts support for string elements is available.
THis is in the case of creating a function that works on some data and returns something but ts is not knowledgeable of the type or components of the returned data.
function mergeObj<T extends object, U extends object>(
objA: T,
objB: U
) {
return Object.assign(objA, objB);
}
const mergedObj = mergeObj({ name: 'han' }, { age: 18 });
This informs ts that the first param is of a certain type, same for the second and the returned value, <>
is therefore a concat of the 2. This therefore offers support when eg extracting components of the returned value.
In the sample code below, the keyof
ensures that whenever the function is called, the second argument is always a key of the first argument as described in the generic func:
function extractValue<T extends object, U extends keyof T>(
obj: T,
key: U
) {
return `Value ${obj[key]}`;
}
extractValue({ age: 20 }, 'age');
Creating flexible & type safe classes that can be constrained to a particular data type and work only with that:
class SomeStorage<T> {
private data: T[] = [];
addItem(item: T) {
this.data.push(item);
}
get storageData() {
return this.data;
}
}
const strge = new SomeStorage<string>();
strge.addItem('chips');
console.log(strge.storageData);
const storage2 = new SomeStorage<number>();
storage2.addItem(1);
There are extra utility types in ts including Partial
& Readonly
.
Only allows the content tot be read and not manipulated:
const ages: Readonly<number[]> = [12, 18, 22];
Allows creation of an item of a certain type or interface without full implementation as the creation is done step by step and at the end the result is converted to the target non-partial object.
interface CompleteInter {
a: string;
b: number;
c: Date;
}
function createAbc(a: string, b: number, c: Date): CompleteInter {
let completeInter: Partial<CompleteInter> = {};
completeInter.a = a;
completeInter.b = b;
completeInter.c = c;
return completeInter as CompleteInter;
}
More for developers than users. It's an aspect of metaprogramming
for soing some actions behind the scenes. When more than 1 is used, they are executed bottom up, ie the closest one to the class gets called first.
In terms of classes, they are called when the class is initialized and not when istantiated.
function Logger(constructor: Function) {
console.log('Decorator called');
console.log(constructor); // Logs the whole of SomeClass
}
@Logger
class SomeClass {
constructor() {
console.log('Class instantiated...');
}
}
new SomeClass();
THe decorator is contained in another function that returns it thus allowing passing of auguments to the decorator. When using factories, they are called as normal functions and therefore their execution is per the order in the code. But the returned decorators still follow the bottom up exec.
function Logger(text: string) {
return function (constructor: Function) {
console.log('Decorator called' + text);
console.log(constructor);
};
}
@Logger('some text')
This involves using decorators with other properties, not classes. They take in 2 params:
- The target - Which can be the prototype of the object when dealing with instance & a constructor when dealing with statics, etc that holds the property called on.
- Name - Property name - variable name.
function Logger(target: any, propName: string | Symbol) {
console.log(target, propName);
}
class Car {
@Logger
design: string;
constructor(d: string) {
this.design = d;
}
}
Adds a third param in decorators for getters & setters like functions to print their info:
function Accesser(
target: any,
propName: string | Symbol,
descriptor: PropertyDescriptor
) {
//
}
// Later in class
class SomeClass {
@Accesser
set theDesign(design: string) {
this.design = design;
}
}
The parameter decos have a difference in params, for one, the second param name
is that of the function that contains it and second, the third param is an index for the parameter in the function:
function ParamDecorator(
target: any,
propName: string | Symbol,
position: number
) {
//
}
class Car {
someFunc(@ParamDecorator param: string) {
console.log(param);
}
}
SImilar to the accesor ones with a difference in the descriptor
contents. The syntax remains the same though, and is added before a class method. LOOK INTO PROPERTY DESCRIPTORS! (configurable, enumerable, value, writable)
Class decorators can return a constructor funtion that replaces the original one in the class, or add functionality to it. This constructor can work just like any other with logic that might be needed whenever the class is instantiated. The logic is as below:
function Logger() {
return function <
T extends { new (...args: any[]): { name: string } }
>(originalonstructor: T) {
return class extends originalonstructor {
constructor(..._: any) {
super();
// logic for when instantiation happens
}
};
};
}
@Logger()
class SomeClass {
constructor(public name: string) {
console.log('Class instantiated...');
}
}
new SomeClass('person');
Returns from property & parameter decorators are ignored thus have no effect. But from methods and getters and setters, you can return and edit the default values for the configurable, enumarable and such. An example for a method decorator return can be:
function Binder(_: any, _2: string, descriptor: PropertyDescriptor) {
const origiMethod = descriptor.value;
const newDescriptor: PropertyDescriptor = {
configurable: true,
enumerable: true,
get() {
const boundFn = origiMethod.bind(this);
return boundFn;
},
};
return newDescriptor;
}
What this does is that it ensures the originalMethod
from which the decorator is called, always has its this
bound to the function itself as the newDescriptor
that is returned replaces the old one. This can be an encapsulation and help prevent binding
the method whenever it is called by other items.
// TODO - Review the drag drop project
Including code in a similar namespace aallows for code splitting through exorts between files. Ensure the "outFile": "./dist/bundle.js"
in the tsconfig
is enabled refering to where the bundled file will be compiled to as js
does not support namespaces. Also set the "module": "commonjs"
to something like amd
or system
which support outFile
instead of commonjs.
Therefore in the exporting file:
namespace namespaceName {
export const obj = {};
export class MyClass {}
export enum nums {
one,
two,
three,
}
export const val: number = 1;
}
Then in the importing file, the namespace must match the exporting namespace and add a special signal at the file beginning which starts with 3 slashes and is not a regular comment:
/// <reference path='exportingFileName.ts'/>
namespace namespaceName {
// Use the exported items
}
Done in the original js
es export import routine.
// Exporter
export const item = {};
// Importer
import { namedExport } from './path/to/file.js';
The imported file should be a .js
file since the import is after compilation. Also to note is that es modules
are not supported by the outFile
therefore it should be commented out and the module set bck to something like commonjs
. Also set the type="module"
in the script tag in the html.
npm install --save-dev webpack webpack-cli webpack-dev-server typescript ts-server clean-webpack-plugin
ts-loader
informs webpack on how to convert ts
to js
so that webpack
can do both the compilation and the bundling of the compiled js.
Start by removing the .js
extension in all imports. The configure a file webpack.config.js
s it is in the files. Also add a script to trigger webpack in the package.json
:
script{
"start": "webpack-dev-server",
"build": "webpack"
}
You can also set up a webpack.config for production as in webpack.config.prod.js
, name as you please.
clean-webpack-plugin
is used to delete all compiled files whenever the code changes. Implementation is in as the above file. Thereafter tweak the build
script with the new prod file name:
script{
"build": "webpack --config webpack.config.prod.js"
}
To use 3rd parties written in vanilla js in ts, use its types
. Example with lodash
, install it together with its types (lodash types):
npm i --save-dev @types/lodash
The types are normally @types/package-name
.
If a package's types do not exist, for one you can use the declare
keyword to ascertain to ts that it will exist. Example with a var x
in the script
tag in html that has been inititalized, ts can be made awae of it and use it by using:
declare var x: any;
- class-transformers - Transfoeming objects to native classes.
- class-validator
In the tsconfig:
{
"moduleResolution": "node",
"outDir": "./dist",
"rootDir": "./src"
}
Install @types/node
, @types/express
.
-
Importing html elements to js:
const importNode = document.importNode( SelectedHTMLElement.content, true );
-
Draggers
dragStartHandler(event: DragEvent){ // setData(format, dataToSet) event.dataTransfer!.setData('text/plain', identifierForTheDraggedData); // For cursor and browser indications event.dataTransfer!.effectAllowed = 'move'; // Or copy } dragEndHandler(){} dragOverHandler(event: DragEvent){ if(event.dataTransfer && event.dataTransfer.types[0] === 'text/plain'){ event.preventDefault() // Adding droppable classes for visual effects } } dragLeaveHandler(){ // Removing droppable classes for visual effects } dropHandler(e: DragEvent){ itemId = e.dataTransfer!.getData('text/plain') }