Skip to content

dmytromykhailiuk/Learn_TypeScript

Folders and files

NameName
Last commit message
Last commit date

Latest commit

Β 

History

33 Commits
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 

Repository files navigation

TypeScript

- is a superset of JavaScript which primarily provides optional static typing, classes and interfaces. One of the big benefits is to enable IDEs to provide a richer environment for spotting common errors as you type the code.


🐰 TypeScript Data types: 🐯

Objects

type Obl = {
  name: string;
  age: number;
  isStudent?: boolean;
  position: string
}


let obj: Obl = {
  name: 'Ivan',
  age: 30,
  position: 'sinior'
}

Functions

let sum: (a: number, b: number) => number;

sum = function(a: number, b: number): number {
  return a + b;
}

// console.log(sum(2, 6));

//---------------------------------------

function log(something: number | string):void {
  console.log(something);
}

// log(46);

//------------------------------------------

function throwError():never {
  throw new Error
}

// throwError();

//----------------------------------------

let a: unknown;

a = 2332;
a = 'sdsds'
let b: string
// b = a; // Error

if (typeof a === "string") {
  b = a // Valid
}

Literal Types

let sex: 'men' | 'women';


//sex = 323; ERROR only 'men' | 'women'
sex = 'men'

Aliases & Custom Types

type Sex = 'men' | 'women';

let person: Sex;

person = 'women';

//----------------------

type Something = string | number;

let something: Something;

something = '';

// something = true; // - can't be

Typles

let arr:[string, number] = ['author', 2];


//arr = ['dfdfd', 23, true]
arr = ['dfdfd', 23]; //only

Enum

enum Role {AUTHOR = 'Author', SECOND = 2} // AUTHOR = 'Author' | SECOND = 2

let role = Role[2]; // Second

//console.log(role);

enum SportCar {Ford, Ferrari} // Ford = 0 | Ferrari = 1

let sportCar = SportCar.Ford;

// console.log(sportCar);

enum Car {Mazda = 34, Lada} // Mazda = 34 | Lada = 35

let car = Car.Lada;

console.log(car);


🐼 Classes & Interfaces: 🐡

Basic Classes

class Department {
  private id: string;
  private name: string;

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





//-------------------SHORT SYNTAX--------------------------------------

class Department1 {
  constructor(private id: string, private name: string) {} // This equal to previous
}





//---------PUBLIC----------This is the default type---------------------

class Department2 {
  constructor(public id: number, public name: string) {}

  public increaseId(val: number) {
    this.id += val;
  }
}

const department2 = new Department2(3, 'IT');
//console.log(department2.id); // 3

department2.id = 6;
// console.log(department2.id); // 6

department2.increaseId(12);
// console.log(department2.id); // 18





//------------PRIVATE-----Can only be called within a class---------------

class Department3 {
  constructor(private id: number, private name: string) {}

  private increaseId(val: number) {
    this.id += val;
  }

  public logName():void {
    console.log(this.name);
  } 
}

const department3 = new Department3(3, 'IT');
//console.log(department3.id); // 3 - Error // can not call private proprty

//department3.id = 6; // Error // can not change private proprty
// console.log(department3.id); // 3 - can not change private property

//console.log(department3.increaseId(12)); // undefined ---- becouse can not call private method

//console.log(department3.name); // IT - Error // can not call private proprty
//department3.logName(); // IT 





//---------------READONLY properties-------------------------------------

class Department4 {
  constructor(public readonly id: number, public readonly name: string) {} 
}

const newDepartment = new Department4(41, "Dimon");


// newDepartment.id = 2 // Cant change readonly property
// console.log(newDepartment.id); // but can read

// newDepartment.name = 'Dmytro' // Cant change readonly property
// console.log(newDepartment.name); // but can read

Inheritance

//--------------------Inheritance--------------------------------

class Animal {
  constructor(private name: string, public canFly: boolean) {}
}

class Dog extends Animal {
  constructor(name: string) {
    super(name, false)
  }

  // public getName():void {
  //   console.log(this.name); // name is Animal private property and Dod does't have name
  // }

  public isCanFly():void {
    console.log(this.canFly);
  }
}

const rex = new Dog('Rex');
// rex.isCanFly() // false




//------------------Protrcted Props amd Methods------------------------------

class Animal1 {
  constructor(protected name: string, public canFly: boolean) {}

  public getName():void {
    console.log(this.name);
  }

  protected isCanFly():void {
    console.log(this.canFly);
  }
}


class Dog1 extends Animal1 {
  constructor(name: string) {
    super(name, false)
  }
}

const bobic = new Dog1('bobic');
// bobic.getName(); // Works!!!
// bobic.isCanFly(); // Dostn't work!!! Becouse it's child's private method

const animal1 = new Animal('shark', false);
// animal1.isCanFly(); // Dostn't work!!! Becouse it's not your own method





//-------------------------Getters & Setters---------------------------


class Animal2 {
  constructor(public _name: string) {}


  get name() {
    return this._name
  }
  
  set name(value: string) {
    if (!value) {
      throw new Error('Please pass in a valid name!');
    }
    this._name= value;
  }
}

const fly = new Animal2('bzz');
// console.log(fly.name); // bzz

fly.name = 'Myyy'; // set value like a property
// console.log(fly.name); // Myyy





//------------------Static------We dont need to create instance--------
//------------------------------We can use class metods and properties-

class Animal3 {
  static genus: string[] = [];
  
  constructor(public name: string) {}

  static addNewgenus(...newGenus: string[]) {
    Animal3.genus.push(...newGenus);
  }
}

// console.log(Animal3.genus); // []

Animal3.addNewgenus('mammals', 'birds', 'fishes'); 
//console.log(Animal3.genus); // ["mammals", "birds", "fishes"]






//-----------Abstract Classes-----------------------------------------

abstract class Department5 {

  constructor(protected readonly id: number, public name: string) {}

  abstract describe(this: Department): void;
}

class ITDepartment5 extends Department5 {

  admins: string[];

  constructor(id: number, admins: string[]) {
    super(id, 'IT');
    this.admins = admins;
  }

  describe() {
    console.log('IT Department - ID: ' + this.id);
  }
}

// const newDepartment5 = new Department5(21, 'l') // we can't create instance of Abstract Class

// Abstract class is used as a pattern !!!!

const newITDepartment5 = new ITDepartment5(21, []);
// newITDepartment5.describe(); // IT Department - ID: 21





//-------------------------Singleton------------------------------------

class Singleton {
  private static instance: Singleton;

  private constructor() { }

  public static getInstance(): Singleton {
    if (!Singleton.instance) {
      Singleton.instance = new Singleton();
    }
    return Singleton.instance;
  }

  public someBusinessLogic() {

  }
}


function clientCode() {
  const s1 = Singleton.getInstance();
  const s2 = Singleton.getInstance();

  if (s1 === s2) {
    console.log('Singleton works, both variables contain the same instance.');
  } else {
    console.log('Singleton failed, variables contain different instances.');
  }
}

// clientCode(); // Singleton works, both variables contain the same instance.

Interfaces

//-----------------Interfaces-----------------------

interface Card {
  readonly id: number
  img: string
  title: string
  log(str: string): void  // method interface 
  description?: string
}

const card: Card = {
  id: 1, // readonly pronerty, becouse iterface has
  img: '/',
  title: 'Some card',
  log: (str: string) => console.log(str)
}

//card.id = 12 // id - readonly // We can't change

// class CardClass implements Card{
//   constructor(public id: number, public title: string, private img: string) {}

//   log(str: string){
//     console.log(str);
//   }
  
// } // Class invalid becouse img - private





//------------Interfaces with function--------------------------

interface Log {
  (name: string, surname: string):void
}
let logFullName: Log

logFullName = (name: string, surname: string): void => {
  console.log(name + ' ' + surname);  
}  //  valid

// class LogClass1 implements Log {
//   log(a:string, b:string):void{     // Invalid
//     console.log(a + ' ' + b);       // We can't use function interface for class
//   }
// }




//--------------------Extending Interfaces---------------------------

interface Named {
  name: string
}

interface Aged {
  age?: number
}

interface Gritable extends Named, Aged { 
  // can extends from more than 1 iterfaces
  // but only interface can

  greet(phase: string):void
}

interface WhatAreYouLookingFor {
  whatAreYouLookingFor():void
}


class Consultant implements Gritable, WhatAreYouLookingFor {  
  // can implements from more than 1 iterfaces

  constructor(public name: string, public age: number) {}

  greet(str: string){
    console.log('Hi! Welcome in ours shop');
  }

  whatAreYouLookingFor(){
    console.log("What are you looking for"); 
  }
}


πŸ– Advanced Typing Concepts: 🐒

Interseption Types

type Admin = {
  name: string;
  privileges: string[];
};

type Employee = {
  name: string;
  startDate: Date;
};

// interface ElevatedEmployee extends Employee, Admin {}

type ElevatedEmployee = Admin & Employee;

const e1: ElevatedEmployee = {
  name: 'Max',
  privileges: ['create-server'],
  startDate: new Date()
};

type Combinable = string | number;
type Numeric = number | boolean;

type Universal = Combinable & Numeric;

Function Overloads

function add(a: number, b: number): number;
function add(a: string, b: string): string;
function add(a: string, b: number): string;
function add(a: number, b: string): string;
function add(a: Combinable, b: Combinable) {
  if (typeof a === 'string' || typeof b === 'string') {
    return a.toString() + b.toString();
  }
  return a + b;
}

const result = add('Max', ' Schwarz');
result.split(' ');

Optionl Chaning

const fetchedUserData1 = {
  id: 'u1',
  name: 'Max',
  //job: { title: 'CEO', description: 'My own company' }
};

// console.log(fetchedUserData1?.job?.title); // undefined insted of Error

Nullish Coalescing

const userInput = undefined;

const storedData = userInput ?? 'DEFAULT'; // Only for null value - ??

console.log(storedData); // DEFAULT

Type Guard

type UnknownEmployee = Employee | Admin;

function printEmployeeInformation(emp: UnknownEmployee) {
  console.log('Name: ' + emp.name);
  if ('privileges' in emp) {
    console.log('Privileges: ' + emp.privileges);
  }
  if ('startDate' in emp) {
    console.log('Start Date: ' + emp.startDate);
  }
}

printEmployeeInformation({ name: 'Manu', startDate: new Date() });

class Car5 {
  drive() {
    console.log('Driving...');
  }
}

class Truck {
  drive() {
    console.log('Driving a truck...');
  }

  loadCargo(amount: number) {
    console.log('Loading cargo ...' + amount);
  }
}

type Vehicle = Car5 | Truck;

const v1 = new Car5();
const v2 = new Truck();

function useVehicle(vehicle: Vehicle) {
  vehicle.drive();
  if (vehicle instanceof Truck) {
    vehicle.loadCargo(1000);
  }
}

useVehicle(v1);
useVehicle(v2);

Discriminated Union

interface Bird {
  type: 'bird';
  flyingSpeed: number;
}

interface Horse {
  type: 'horse';
  runningSpeed: number;
}

type Animal5 = Bird | Horse;

function moveAnimal(animal: Animal5) {
  let speed;
  switch (animal.type) {
    case 'bird':
      speed = animal.flyingSpeed;
      break;
    case 'horse':
      speed = animal.runningSpeed;
  }
  console.log('Moving at speed: ' + speed);
}

moveAnimal({type: 'bird', flyingSpeed: 10});

Type Casting

// const userInputElement = <HTMLInputElement>document.getElementById('user-input')!;
const userInputElement = document.getElementById('user-input')! as HTMLInputElement;

if (userInputElement) {
  (userInputElement as HTMLInputElement).value = 'Hi there!';
}

Index Properties

interface ErrorContainer { // { email: 'Not a valid email', username: 'Must start with a character!' }
  [prop: string]: string;
}

const errorBag: ErrorContainer = {
  email: 'Not a valid email!',
  username: 'Must start with a capital character!'
};


🐢 Generics Types: 🐌

Simple Generics Type

const names: Array<string> = []; // string[]
// names[0].split(' ');

const promise: Promise<number> = new Promise((resolve, reject) => {
  setTimeout(() => {
    resolve(10);
  }, 2000);
});

promise.then(data => {
  // data.split(' ');
})

function merge<T extends object, U extends object>(objA: T, objB: U) {
  return Object.assign(objA, objB);
}

const mergedObj = merge({ name: 'Max', hobbies: ['Sports'] }, { age: 30 });
console.log(mergedObj);

Constrains

interface Lengthy {
  length: number;
}

function countAndDescribe<T extends Lengthy>(element: T): [T, string] {
  let descriptionText = 'Got no value.';
  if (element.length === 1) {
    descriptionText = 'Got 1 element.';
  } else if (element.length > 1) {
    descriptionText = 'Got ' + element.length + ' elements.';
  }
  return [element, descriptionText];
}

console.log(countAndDescribe(['Sports', 'Cooking']));

Another Generic Function

function extractAndConvert<T extends object, U extends keyof T>(obj: T,key: U) {
  return 'Value: ' + obj[key];
}

extractAndConvert({ name: 'Max' }, 'name');

Generic Classes

class DataStorage<T extends string | number | boolean> {
  private data: T[] = [];

  addItem(item: T) {
    this.data.push(item);
  }

  removeItem(item: T) {
    if (this.data.indexOf(item) === -1) {
      return;
    }
    this.data.splice(this.data.indexOf(item), 1); // -1
  }

  getItems() {
    return [...this.data];
  }
}

const textStorage = new DataStorage<string>();   // set string generic
textStorage.addItem('Max');
textStorage.addItem('Manu');
textStorage.removeItem('Max');
console.log(textStorage.getItems());

const numberStorage = new DataStorage<number>(); // set number generic

// const objStorage = new DataStorage<object>();
// const maxObj = {name: 'Max'};
// objStorage.addItem(maxObj);
// objStorage.addItem({name: 'Manu'});
// // ...
// objStorage.removeItem(maxObj);
// console.log(objStorage.getItems());

Generic Utility Types

interface CourseGoal {
  title: string;
  description: string;
  completeUntil: Date;
}

function createCourseGoal(title: string, description: string, date: Date): CourseGoal {
  let courseGoal: Partial<CourseGoal> = {};
  courseGoal.title = title;
  courseGoal.description = description;
  courseGoal.completeUntil = date;
  return courseGoal as CourseGoal;
}

const names2: Readonly<string[]> = ['Max', 'Anna'];
// names2.push('Manu');     // Error bocouse readonly
// names2.pop();            // Error bocouse readonly


πŸ‘ Decorators: πŸ€

First Class Decorator

//  Decorator is a simple function that always accept the class constructor.
//  Decorators are using only for classes, classes methods, and properties

function Logger1(constructor: Function) { // Accepting the class constructor
  console.log('Logging...');                      // first log
  console.log(constructor);                       // second log
}

@Logger1   // add decorator to class (always before the declaration)
class Person1 {
  name = 'Max';

  constructor() {
    console.log('Creating person object...');     // third log
  }
}

const pers1 = new Person1();                      

console.log(pers1);                               // fourth log

Decorator Fuctories

//----We need to return the function to pass the parameters in decorator

function Logger2(logString: string) { // Take parameters
  return function(constructor: Function) { // Decorator always must accept the class constructor
    console.log(logString);                                  // log-1
    console.log(constructor);                                // log-2
  };
}

@Logger2('LOGGING - PERSON')   // Now we can pass parameters in decorator 
class Person2 {
  name = 'Dimon';

  constructor() {
    console.log('Creating person object...');                // log-3
  }
}

const pers2 = new Person2();

console.log(pers2);                                          // log-4

Decorator With Temlate

//-----------------Creating custom component structure-----------------

function Logger3(logString: string) {
  return function(constructor: Function) {
    console.log(logString);
    console.log(constructor);
  };
}

function WithTemplate3(template: string, hookId: string) {
  return function(constructor: any) {
    const hookEl = document.getElementById(hookId); // find for IdBlock
    const p = new constructor();
    if (hookEl) {
      hookEl.innerHTML = template;
      hookEl.querySelector('h1')!.textContent = p.name; // Set class poperty in temlate
    }
  }
}

// @Logger3('LOGGING - PERSON')
@WithTemplate3('<h1>My Person Object</h1>', 'app') // Set template in appId block with class poperty
class Person3 {
  name = 'Max';

  constructor() {
    console.log('Creating person object...');
  }
}

const pers3 = new Person3();

console.log(pers3);

Diferent Class Decorators

//--------------------Decorators for different entities-----------------

function Logg(target: any, propertyName: string | Symbol) {  // For property
  //console.log('Property decorator!');
  //console.log(target, propertyName); // target === class constructor, "title"
}

function Logg2(target: any, name: string, descriptor: PropertyDescriptor):void {
  console.log('Accessor decorator!');
  //console.log(target); // target === class constructor
  //console.log(name);
  console.log(descriptor); //  descriptor
}

function Logg3( // For Method
  target: any,  // target === class constructor
  name: string | Symbol,
  descriptor: PropertyDescriptor
) {
  console.log('Method decorator!');
  //console.log(target);
  //console.log(name);
  console.log(descriptor); //   descriptor
}

function Logg4(target: any, name: string | Symbol, position: number) { // For Method Parameter
  //console.log('Parameter decorator!');
  //console.log(target);
  //console.log(name);
  //console.log(position);
}


class Product5 {
  @Logg // Using before entity
  title: string;
  private _price: number;

  @Logg2 // Using before entity
  set price(val: number) {
    if (val > 0) {
      this._price = val;
    } else {
      throw new Error('Invalid price - should be positive!');
    }
  }

  constructor(t: string, p: number) {
    this.title = t;
    this._price = p;
  }

  @Logg3 // Using before entity
  getPriceWithTax(@Logg4 tax: number) { // Using before entity
    return this._price * (1 + tax);
  }
}

Auto Bind Decorator

//------------------Binding This with the help of a decorator-------------


function Autobind7(_: any, _2: string, descriptor: PropertyDescriptor) { 
  const originalMethod = descriptor.value;  
  const adjDescriptor: PropertyDescriptor = {
    configurable: true,
    enumerable: false,
    get() {
      const boundFn = originalMethod.bind(this);
      return boundFn;
    }
  };
  return adjDescriptor;
}

class Printer7 {
  message = 'This works!';

  @Autobind7  
  showMessage() {
    console.log(this.message);
  }
}

const p7 = new Printer7();
p7.showMessage();

const button7 = document.querySelector('button')!;
button7.addEventListener('click', p7.showMessage);

Decorator Validation

//-----------------------Decorator validation---------------------------

interface ValidatorConfig {
  [property: string]: {
    [validatableProp: string]: string[]; // ['required', 'positive']
  };
}

const registeredValidators: ValidatorConfig = {};

function Required(target: any, propName: string) {
  registeredValidators[target.constructor.name] = {
    ...registeredValidators[target.constructor.name],
    [propName]: ['required']
  };
}

function PositiveNumber(target: any, propName: string) {
  registeredValidators[target.constructor.name] = {
    ...registeredValidators[target.constructor.name],
    [propName]: ['positive']
  };
}

function validate(obj: any) {
  const objValidatorConfig = registeredValidators[obj.constructor.name];
  if (!objValidatorConfig) {
    return true;
  }
  let isValid = true;
  for (const prop in objValidatorConfig) {
    for (const validator of objValidatorConfig[prop]) {
      switch (validator) {
        case 'required':
          isValid = isValid && !!obj[prop];
          break;
        case 'positive':
          isValid = isValid && obj[prop] > 0;
          break;
      }
    }
  }
  return isValid;
}

class Course {
  @Required
  title: string;
  @PositiveNumber
  price: number;

  constructor(t: string, p: number) {
    this.title = t;
    this.price = p;
  }
}

const courseForm = document.querySelector('form')!;
courseForm.addEventListener('submit', event => {
  event.preventDefault();
  const titleEl = document.getElementById('title') as HTMLInputElement;
  const priceEl = document.getElementById('price') as HTMLInputElement;

  const title = titleEl.value;
  const price = +priceEl.value;

  const createdCourse = new Course(title, price);

  if (!validate(createdCourse)) {
    alert('Invalid input, please try again!');
    return;
  }
  console.log(createdCourse);
});

About

TypeScript in simple examples

Resources

Stars

Watchers

Forks

Releases

No releases published

Packages

No packages published