├── Chapter10 ├── hello.txt ├── 10_inversify_express_utils │ ├── constants │ │ └── types.ts │ ├── repositories │ │ └── movie_repository.ts │ ├── entities │ │ └── movie.ts │ ├── inversify.config.ts │ ├── index.ts │ └── db.ts ├── 09_mvc │ ├── repositories │ │ └── movie_repository.ts │ ├── entities │ │ └── movie.ts │ ├── index.ts │ ├── db.ts │ └── controllers │ │ └── movie_controller.ts ├── 06_express_hello.ts ├── 05_http_hello.ts ├── readme.md ├── 02_promisify.ts ├── 01_callback.ts ├── 07_express_routing.ts ├── package.json └── 08_express_middleware.ts ├── Chapter09 ├── readme.md ├── src │ ├── main.scss │ ├── operations │ │ ├── validation.ts │ │ ├── add.ts │ │ └── pow.ts │ ├── main_server.ts │ ├── main_browser.ts │ └── calculator.ts ├── index.html ├── test │ ├── add.test.ts │ ├── pow.test.ts │ ├── validation.test.ts │ └── calculator.test.ts ├── gulpfile.js ├── package.json └── tslint.json ├── Chapter14 ├── 02_testing │ ├── readme.md │ ├── src │ │ ├── backend │ │ │ ├── main.ts │ │ │ ├── interfaces.ts │ │ │ └── server.ts │ │ └── frontend │ │ │ ├── math_client.ts │ │ │ ├── main.tsx │ │ │ ├── main.scss │ │ │ └── numeric_input_component.tsx │ ├── tsconfig.e2e.json │ ├── index.html │ ├── globals.js │ ├── jsdom.js │ ├── test │ │ ├── pow.e2e.ts │ │ ├── pow_service.test.ts │ │ ├── match_client.test.ts │ │ └── calculator_component.test.tsx │ ├── nightwatch.json │ └── tslint.json └── 01_assertions │ ├── application.ts │ └── assertions.ts ├── Chapter12 ├── web │ ├── frontend │ │ ├── app.scss │ │ ├── app.component.ts │ │ ├── config │ │ │ ├── types.ts │ │ │ ├── layout.component.ts │ │ │ └── layout.module.ts │ │ ├── components │ │ │ ├── error.component.ts │ │ │ ├── loading.component.ts │ │ │ ├── listgroup.component.ts │ │ │ ├── textfield.component.ts │ │ │ ├── button.component.ts │ │ │ ├── header.component.ts │ │ │ ├── grid.component.ts │ │ │ ├── components.module.ts │ │ │ ├── card.component.ts │ │ │ └── modal.component.ts │ │ ├── main.ts │ │ ├── index.html │ │ ├── pages │ │ │ ├── homepage.module.ts │ │ │ ├── actorspage.module.ts │ │ │ ├── moviespage.module.ts │ │ │ └── homepage.component.ts │ │ ├── interfaces.ts │ │ ├── app.module.ts │ │ ├── app-routing.module.ts │ │ └── services │ │ │ ├── movie_service.ts │ │ │ └── actor_service.ts │ ├── universal │ │ └── entities │ │ │ ├── actor.ts │ │ │ └── movie.ts │ └── backend │ │ ├── constants │ │ └── types.ts │ │ ├── repositories │ │ ├── actor_repository.ts │ │ └── movie_repository.ts │ │ ├── entities │ │ ├── movie.ts │ │ └── actor.ts │ │ ├── db.ts │ │ ├── inversify.config.ts │ │ ├── index.ts │ │ └── controllers │ │ ├── movie_controller.ts │ │ └── actor_controller.ts ├── tsconfig.json ├── package.json └── tslint.json ├── Chapter05 ├── 06_es6_module_import.ts ├── 07_legacy_modules_export.ts ├── 08_legacy_modules_import.ts ├── 01_namespaces.ts ├── 05_e6_modules_export.ts ├── 02_nested_namespaces.ts ├── 03_multifile_namespace.ts ├── 04_periods.ts ├── 10_ioc_containers.ts └── 09_inversion_vs_injection.ts ├── Chapter11 ├── web │ ├── universal │ │ └── entities │ │ │ ├── actor.ts │ │ │ └── movie.ts │ ├── frontend │ │ ├── contants │ │ │ └── types.ts │ │ ├── config │ │ │ ├── ioc.ts │ │ │ └── layout.tsx │ │ ├── components │ │ │ ├── error_msg_component.tsx │ │ │ ├── loading_component.tsx │ │ │ ├── counter.tsx │ │ │ ├── card_component.tsx │ │ │ ├── list_group_component.tsx │ │ │ ├── header_component.tsx │ │ │ ├── button_component.tsx │ │ │ ├── textfield_component.tsx │ │ │ └── grid_component.tsx │ │ ├── index.html │ │ ├── index.tsx │ │ ├── pages │ │ │ └── home_page.tsx │ │ └── interfaces.ts │ └── backend │ │ ├── constants │ │ └── types.ts │ │ ├── repositories │ │ ├── actor_repository.ts │ │ └── movie_repository.ts │ │ ├── entities │ │ ├── movie.ts │ │ └── actor.ts │ │ ├── db.ts │ │ ├── inversify.config.ts │ │ ├── index.ts │ │ └── controllers │ │ ├── movie_controller.ts │ │ └── actor_controller.ts ├── tsconfig.json ├── package.json └── tslint.json ├── Chapter15 ├── app │ ├── interfaces.ts │ ├── main.ts │ ├── broken.ts │ ├── ninja.ts │ └── katana.ts ├── package.json ├── 02_ast_navigation.ts ├── 06_language_services.ts └── 03_ast_diagnostics.ts ├── Chapter07 ├── 10_recursion.ts ├── 02_lambdas.ts ├── 03_function_types.ts ├── 14_ramda.ts ├── 08_pipes.ts ├── 01_pure_functions.ts ├── 13_immutable.ts ├── 04_higher_order_function.ts ├── 05_composition.ts ├── 07_currying.ts └── 09_pointfree_style.ts ├── Chapter01 ├── 04_scope.ts ├── 03_tuples.ts ├── 01_type_inference.ts ├── 02_variables.ts ├── 05_spread_operator.ts ├── 09_interfaces.ts ├── 08_classes.ts ├── 10_modules.ts ├── 07_functions.ts └── 11_geometry.ts ├── Chapter02 ├── 05_type_aliases.ts ├── 10_type_guards.ts ├── 18_weak_types.ts ├── 09_typeof_operator.ts ├── 22_type_casting.ts ├── 19_keyof_operator.ts ├── 13_literal_types.ts ├── 20_index_signature.ts ├── 02_type_annotations.ts ├── 01_type_inference.ts ├── 23_generic_types.ts ├── 12_control_flow_analysis.ts ├── 21_local_types.ts ├── 17_object_literals.ts ├── 16_enumerations.ts ├── 07_nullable_types.ts ├── 26_lookup_types.ts ├── 08_non_nullable_types.ts ├── 04_union_types.ts ├── 03_structural_type_system.ts ├── 27_mapped_type_modifiers.ts ├── 14_discriminated_unions.ts ├── 15_never_type.ts ├── 06_intersection_types.ts ├── 25_mapped_types.ts ├── 24_generic_constraints.ts ├── 28_conditional_types.ts └── 29_polymorphic_this_type.ts ├── Chapter06 ├── 05_prototype_chain.ts ├── 01_stack.ts ├── 04_prototypes.ts ├── 03_bind.ts └── 02_function_calls.ts ├── Chapter13 ├── 02_try_catch.ts └── 01_exception.ts ├── Chapter03 ├── 05_default_parameters.ts ├── 01_function_declaration.ts ├── 08_specialized_overloading.ts ├── 02_function_types.ts ├── 12_arrow_functions.ts ├── 03_trailing_comas.ts ├── 04_optional_parameters.ts ├── 09_function_scope.ts ├── 15_covariant_arguments.ts ├── 11_tag_templates.ts ├── 19_async_iterators.ts ├── 18_async_generators.ts ├── 06_rest_parameters.ts ├── 16_generators.ts ├── 20_delegate_generators.ts └── 07_function_overloading.ts ├── Chapter04 ├── 15_interfaces.ts ├── 05_class_expressions.ts ├── 04_parameter_properties.ts ├── 19_polymorphism.ts ├── 07_optional_properties.ts ├── 20_isp.ts ├── 13_iterables.ts ├── 17_encapsulation.ts ├── 14_abstract_classes.ts ├── 09_method_overriding.ts ├── 21_lsp.ts ├── 08_readonly_properties.ts ├── 02_Inheritance.ts ├── 06_static_members.ts ├── 16_srp.ts └── 18_ocp.ts ├── Chapter08 ├── 05_decorator_options.ts ├── 01_class_decorator.ts ├── 04_parameter_decorator.ts ├── 02_method_decorator.ts ├── 03_property_decorator.ts └── 06_reflect_metadata.ts ├── tsconfig.json ├── tslint.json ├── package.json └── LICENSE /Chapter10/hello.txt: -------------------------------------------------------------------------------- 1 | hello world! -------------------------------------------------------------------------------- /Chapter09/readme.md: -------------------------------------------------------------------------------- 1 | # Chapter 09 2 | 3 | -------------------------------------------------------------------------------- /Chapter14/02_testing/readme.md: -------------------------------------------------------------------------------- 1 | # Chapter 14 2 | 3 | -------------------------------------------------------------------------------- /Chapter12/web/frontend/app.scss: -------------------------------------------------------------------------------- 1 | main { 2 | padding-top: 60px 3 | } 4 | -------------------------------------------------------------------------------- /Chapter05/06_es6_module_import.ts: -------------------------------------------------------------------------------- 1 | import { UserModel } from "./05_e6_modules_export"; 2 | -------------------------------------------------------------------------------- /Chapter05/07_legacy_modules_export.ts: -------------------------------------------------------------------------------- 1 | class User { 2 | // … 3 | } 4 | export = User; 5 | -------------------------------------------------------------------------------- /Chapter05/08_legacy_modules_import.ts: -------------------------------------------------------------------------------- 1 | import User = require("./07_legacy_modules_export"); 2 | -------------------------------------------------------------------------------- /Chapter09/src/main.scss: -------------------------------------------------------------------------------- 1 | $bgColor: #e0e0e0; 2 | 3 | body { 4 | background-color: $bgColor; 5 | } 6 | -------------------------------------------------------------------------------- /Chapter05/01_namespaces.ts: -------------------------------------------------------------------------------- 1 | namespace Models { 2 | export class UserModel { 3 | // ... 4 | } 5 | } 6 | -------------------------------------------------------------------------------- /Chapter10/10_inversify_express_utils/constants/types.ts: -------------------------------------------------------------------------------- 1 | export const TYPE = { 2 | MovieRepository: Symbol("MovieRepository") 3 | }; 4 | -------------------------------------------------------------------------------- /Chapter11/web/universal/entities/actor.ts: -------------------------------------------------------------------------------- 1 | export interface ActorInterface { 2 | id: number; 3 | name: string; 4 | yearBorn: number; 5 | } 6 | -------------------------------------------------------------------------------- /Chapter11/web/universal/entities/movie.ts: -------------------------------------------------------------------------------- 1 | export interface MovieInterface { 2 | id: number; 3 | title: string; 4 | year: number; 5 | } 6 | -------------------------------------------------------------------------------- /Chapter12/web/universal/entities/actor.ts: -------------------------------------------------------------------------------- 1 | export interface ActorInterface { 2 | id: number; 3 | name: string; 4 | yearBorn: number; 5 | } 6 | -------------------------------------------------------------------------------- /Chapter12/web/universal/entities/movie.ts: -------------------------------------------------------------------------------- 1 | export interface MovieInterface { 2 | id: number; 3 | title: string; 4 | year: number; 5 | } 6 | -------------------------------------------------------------------------------- /Chapter11/web/frontend/contants/types.ts: -------------------------------------------------------------------------------- 1 | export const TYPE = { 2 | ActorStore: Symbol("ActorStore"), 3 | MovieStore: Symbol("MovieStore") 4 | }; 5 | -------------------------------------------------------------------------------- /Chapter05/05_e6_modules_export.ts: -------------------------------------------------------------------------------- 1 | class UserModel { 2 | // ... 3 | } 4 | 5 | export class TalkModel { 6 | // ... 7 | } 8 | 9 | export { UserModel }; 10 | -------------------------------------------------------------------------------- /Chapter15/app/interfaces.ts: -------------------------------------------------------------------------------- 1 | export interface Weapon { 2 | tryHit(fromDistance: number): boolean; 3 | } 4 | 5 | export interface Named { 6 | name: string; 7 | } 8 | -------------------------------------------------------------------------------- /Chapter15/app/main.ts: -------------------------------------------------------------------------------- 1 | import { Ninja } from "./ninja"; 2 | import { Katana } from "./katana"; 3 | 4 | const ninja = new Ninja(new Katana()); 5 | 6 | ninja.fight(5); 7 | -------------------------------------------------------------------------------- /Chapter11/web/backend/constants/types.ts: -------------------------------------------------------------------------------- 1 | export const TYPE = { 2 | ActorRepository: Symbol("ActorRepository"), 3 | MovieRepository: Symbol("MovieRepository") 4 | }; 5 | -------------------------------------------------------------------------------- /Chapter12/web/backend/constants/types.ts: -------------------------------------------------------------------------------- 1 | export const TYPE = { 2 | ActorRepository: Symbol("ActorRepository"), 3 | MovieRepository: Symbol("MovieRepository") 4 | }; 5 | -------------------------------------------------------------------------------- /Chapter15/app/broken.ts: -------------------------------------------------------------------------------- 1 | import { Ninja } from "./ninja"; 2 | import { Katana } from "./katana"; 3 | 4 | const ninja = new Ninja(new Katana()); 5 | 6 | ninja.fight("5"); 7 | -------------------------------------------------------------------------------- /Chapter09/src/operations/validation.ts: -------------------------------------------------------------------------------- 1 | export function isNumber(a: number) { 2 | if (typeof a !== "number") { 3 | throw new Error(`${a} must be a number!`); 4 | } 5 | } 6 | -------------------------------------------------------------------------------- /Chapter07/10_recursion.ts: -------------------------------------------------------------------------------- 1 | namespace recursion { 2 | 3 | const factorial = (n: number): number => 4 | (n === 0) ? 1 : (n * factorial(n - 1)); 5 | 6 | factorial(5); // 120 7 | 8 | } 9 | -------------------------------------------------------------------------------- /Chapter09/src/operations/add.ts: -------------------------------------------------------------------------------- 1 | import { isNumber } from "./validation"; 2 | 3 | export function add(a: number, b: number) { 4 | isNumber(a); 5 | isNumber(b); 6 | return a + b; 7 | } 8 | -------------------------------------------------------------------------------- /Chapter01/04_scope.ts: -------------------------------------------------------------------------------- 1 | namespace scope_demo { 2 | 3 | var myNumber: number = 1; 4 | let isValid: boolean = true; 5 | const apiKey: string = "0E5CE8BD-6341-4CC2-904D-C4A94ACD276E"; 6 | 7 | } 8 | -------------------------------------------------------------------------------- /Chapter07/02_lambdas.ts: -------------------------------------------------------------------------------- 1 | namespace function_expression_demo { 2 | const log = function(arg: any) { console.log(arg); }; 3 | } 4 | 5 | namespace arrow_function_demo { 6 | const log = (arg: any) => console.log(arg); 7 | } 8 | -------------------------------------------------------------------------------- /Chapter01/03_tuples.ts: -------------------------------------------------------------------------------- 1 | namespace tuples_demo { 2 | 3 | let x: [string, number]; 4 | x = ["hello", 10]; // OK 5 | x = ["world", 20]; // OK 6 | x = [10, "hello"]; // Error 7 | x = [20, "world"]; // Error 8 | 9 | } 10 | -------------------------------------------------------------------------------- /Chapter12/web/frontend/app.component.ts: -------------------------------------------------------------------------------- 1 | import { Component } from "@angular/core"; 2 | 3 | @Component({ 4 | selector: "app-root", 5 | template: ` 6 | `, 7 | }) 8 | export class AppComponent { 9 | } 10 | -------------------------------------------------------------------------------- /Chapter14/02_testing/src/backend/main.ts: -------------------------------------------------------------------------------- 1 | import { getApp } from "./server"; 2 | 3 | const app = getApp(); 4 | const port = 3000; 5 | 6 | app.listen(port, () => { 7 | console.log(`App listening at http://localhost:${port}`); // tslint:disable-line 8 | }); 9 | -------------------------------------------------------------------------------- /Chapter02/05_type_aliases.ts: -------------------------------------------------------------------------------- 1 | // Don't forget to disable strictNullChecks or strict in tsconfig.json 2 | namespace type_aliases_demo { 3 | type PrimitiveArray = Array; 4 | type MyNumber = number; 5 | type Callback = () => void; 6 | } 7 | -------------------------------------------------------------------------------- /Chapter02/10_type_guards.ts: -------------------------------------------------------------------------------- 1 | namespace type_guards_demo { 2 | 3 | let x: any = { /* ... */ }; 4 | 5 | if (typeof x === "string") { 6 | console.log(x.splice(3, 1)); // Error (x is string) 7 | } 8 | 9 | x.foo(); // OK (x is any) 10 | 11 | } 12 | -------------------------------------------------------------------------------- /Chapter14/02_testing/tsconfig.e2e.json: -------------------------------------------------------------------------------- 1 | { 2 | "compilerOptions": { 3 | "outDir": "./dist/" 4 | }, 5 | "extends": "./tsconfig", 6 | "include": [ 7 | "test/*.e2e.ts", 8 | "src/backend/*.ts" 9 | ], 10 | "exclude": [ 11 | "node_modules" 12 | ] 13 | } -------------------------------------------------------------------------------- /Chapter01/01_type_inference.ts: -------------------------------------------------------------------------------- 1 | namespace type_inference_demo { 2 | 3 | let counter1; // unknown (any) type 4 | let counter2 = 0; // number (inferred) 5 | let counter3: number; // number 6 | let counter4: number = 0; // number 7 | 8 | } 9 | -------------------------------------------------------------------------------- /Chapter02/18_weak_types.ts: -------------------------------------------------------------------------------- 1 | namespace weak_type_dmeo { 2 | 3 | interface User { 4 | name?: string; 5 | age?: number; 6 | } 7 | 8 | let user1: User = { name: "Remo", age: 28 }; // OK 9 | let user2: User = { firstName: "Remo", yearBorn: 28 }; // Error 10 | 11 | } 12 | -------------------------------------------------------------------------------- /Chapter14/01_assertions/application.ts: -------------------------------------------------------------------------------- 1 | export class MathApi { 2 | public static pow(base: number, exponent: number) { 3 | let result = base; 4 | for (var i = 1; i < exponent; i++) { 5 | result = result * base; 6 | } 7 | return result; 8 | } 9 | } 10 | -------------------------------------------------------------------------------- /Chapter02/09_typeof_operator.ts: -------------------------------------------------------------------------------- 1 | namespace typeof_operator_demo { 2 | 3 | // typeof at runtime 4 | let myNumber1 = 5; 5 | console.log(typeof myNumber1 === "number"); // true 6 | 7 | // typeof at design time 8 | let myNumber2 = 5; 9 | type NumberType = typeof myNumber2; 10 | 11 | } 12 | -------------------------------------------------------------------------------- /Chapter10/09_mvc/repositories/movie_repository.ts: -------------------------------------------------------------------------------- 1 | import { getConnection } from "typeorm"; 2 | import { Movie } from "../entities/movie"; 3 | 4 | export function getRepository() { 5 | const conn = getConnection(); 6 | const movieRepository = conn.getRepository(Movie); 7 | return movieRepository; 8 | } 9 | -------------------------------------------------------------------------------- /Chapter12/web/frontend/config/types.ts: -------------------------------------------------------------------------------- 1 | import { InjectionToken } from "@angular/core"; 2 | import { MovieService, ActorService } from "../interfaces"; 3 | 4 | export const ACTOR_SERVICE = new InjectionToken("ActorService"); 5 | export const MOVIE_SERVICE = new InjectionToken("MovieService"); 6 | -------------------------------------------------------------------------------- /Chapter11/web/backend/repositories/actor_repository.ts: -------------------------------------------------------------------------------- 1 | import { getConnection } from "typeorm"; 2 | import { Actor } from "../entities/actor"; 3 | 4 | export function getActorRepository() { 5 | const conn = getConnection(); 6 | const movieRepository = conn.getRepository(Actor); 7 | return movieRepository; 8 | } 9 | -------------------------------------------------------------------------------- /Chapter11/web/backend/repositories/movie_repository.ts: -------------------------------------------------------------------------------- 1 | import { getConnection } from "typeorm"; 2 | import { Movie } from "../entities/movie"; 3 | 4 | export function getMovieRepository() { 5 | const conn = getConnection(); 6 | const movieRepository = conn.getRepository(Movie); 7 | return movieRepository; 8 | } 9 | -------------------------------------------------------------------------------- /Chapter12/web/backend/repositories/actor_repository.ts: -------------------------------------------------------------------------------- 1 | import { getConnection } from "typeorm"; 2 | import { Actor } from "../entities/actor"; 3 | 4 | export function getActorRepository() { 5 | const conn = getConnection(); 6 | const movieRepository = conn.getRepository(Actor); 7 | return movieRepository; 8 | } 9 | -------------------------------------------------------------------------------- /Chapter12/web/backend/repositories/movie_repository.ts: -------------------------------------------------------------------------------- 1 | import { getConnection } from "typeorm"; 2 | import { Movie } from "../entities/movie"; 3 | 4 | export function getMovieRepository() { 5 | const conn = getConnection(); 6 | const movieRepository = conn.getRepository(Movie); 7 | return movieRepository; 8 | } 9 | -------------------------------------------------------------------------------- /Chapter14/02_testing/src/frontend/math_client.ts: -------------------------------------------------------------------------------- 1 | export class MathClient { 2 | public async pow(base: number, exponent: number): Promise { 3 | const res = await fetch(`/api/math/pow/${base}/${exponent}`); 4 | const json = await res.json(); 5 | return json.result.toString(); 6 | } 7 | } 8 | -------------------------------------------------------------------------------- /Chapter02/22_type_casting.ts: -------------------------------------------------------------------------------- 1 | namespace type_casting_demo { 2 | 3 | interface SomeType { 4 | // ... 5 | } 6 | 7 | let myObject: SomeType; 8 | let otherObject: any; 9 | myObject = otherObject; // Using <> 10 | myObject = otherObject as SomeType; // Using as keyword 11 | 12 | } 13 | -------------------------------------------------------------------------------- /Chapter06/05_prototype_chain.ts: -------------------------------------------------------------------------------- 1 | namespace chain { 2 | 3 | class Base { 4 | public method1() { return 1; } 5 | public method2() { return 2; } 6 | } 7 | 8 | class Derived extends Base { 9 | public method2() { return 3; } 10 | public method3() { return 4; } 11 | } 12 | 13 | } 14 | -------------------------------------------------------------------------------- /Chapter10/06_express_hello.ts: -------------------------------------------------------------------------------- 1 | import express from "express"; 2 | 3 | const port = 3000; 4 | const app = express(); 5 | 6 | app.get("/", (req, res) => { 7 | res.send("Hello World!"); 8 | }); 9 | 10 | app.listen(port, (error: Error) => { 11 | console.log(`Server running at http://127.0.0.1:${port}/`); 12 | }); 13 | -------------------------------------------------------------------------------- /Chapter13/02_try_catch.ts: -------------------------------------------------------------------------------- 1 | try { 2 | // code that we want to work 3 | throw new Error("Oops!"); 4 | } catch (e) { 5 | // code executed if expected to work fails 6 | console.log(e); 7 | } finally { 8 | // code executed always after try or try and catch (when errors) 9 | console.log("Finally!"); 10 | } 11 | -------------------------------------------------------------------------------- /Chapter10/10_inversify_express_utils/repositories/movie_repository.ts: -------------------------------------------------------------------------------- 1 | import { getConnection } from "typeorm"; 2 | import { Movie } from "../entities/movie"; 3 | 4 | export function getRepository() { 5 | const conn = getConnection(); 6 | const movieRepository = conn.getRepository(Movie); 7 | return movieRepository; 8 | } 9 | -------------------------------------------------------------------------------- /Chapter15/app/ninja.ts: -------------------------------------------------------------------------------- 1 | import { Weapon } from "./interfaces"; 2 | 3 | export class Ninja { 4 | private _weapon: Weapon; 5 | public constructor(weapon: Weapon) { 6 | this._weapon = weapon; 7 | } 8 | public fight(fromDistance: number) { 9 | return this._weapon.tryHit(fromDistance); 10 | } 11 | } 12 | -------------------------------------------------------------------------------- /Chapter09/src/operations/pow.ts: -------------------------------------------------------------------------------- 1 | import { isNumber } from "./validation"; 2 | 3 | export function pow(base: number, exponent: number) { 4 | isNumber(base); 5 | isNumber(exponent); 6 | let result = base; 7 | for (let i = 1; i < exponent; i++) { 8 | result = result * base; 9 | } 10 | return result; 11 | } 12 | -------------------------------------------------------------------------------- /Chapter15/app/katana.ts: -------------------------------------------------------------------------------- 1 | import { Weapon, Named } from "./interfaces"; 2 | 3 | export class BaseWeapon { 4 | damage = 25; 5 | } 6 | 7 | export class Katana extends BaseWeapon implements Weapon, Named { 8 | name = "Katana"; 9 | public tryHit(fromDistance: number) { 10 | return fromDistance <= 2; 11 | } 12 | } 13 | -------------------------------------------------------------------------------- /Chapter10/09_mvc/entities/movie.ts: -------------------------------------------------------------------------------- 1 | import { 2 | Entity, 3 | PrimaryGeneratedColumn, 4 | Column 5 | } from "typeorm"; 6 | 7 | @Entity() 8 | export class Movie { 9 | @PrimaryGeneratedColumn() 10 | public id!: number; 11 | @Column() 12 | public title!: string; 13 | @Column() 14 | public year!: number; 15 | } 16 | -------------------------------------------------------------------------------- /Chapter02/19_keyof_operator.ts: -------------------------------------------------------------------------------- 1 | namespace keyof_operator_demo { 2 | 3 | interface User { 4 | name: string; 5 | age: number; 6 | } 7 | 8 | type userKeys1 = keyof User; // "name" | "age" 9 | 10 | let person = { name: "Remo", age: "28" }; 11 | type userKeys2 = keyof typeof person; // "name" | "age" 12 | 13 | } 14 | -------------------------------------------------------------------------------- /Chapter05/02_nested_namespaces.ts: -------------------------------------------------------------------------------- 1 | namespace App { 2 | export namespace Models { 3 | export class UserModel { 4 | // ... 5 | } 6 | export class TalkModel { 7 | // ... 8 | } 9 | } 10 | } 11 | 12 | const user = new App.Models.UserModel(); 13 | const talk = new App.Models.TalkModel(); 14 | -------------------------------------------------------------------------------- /Chapter03/05_default_parameters.ts: -------------------------------------------------------------------------------- 1 | namespace default_parameters { 2 | 3 | function add1(foo: number, bar: number, foobar?: number): number { 4 | return foo + bar + (foobar !== undefined ? foobar : 0); 5 | } 6 | 7 | function add2(foo: number, bar: number, foobar: number = 0): number { 8 | return foo + bar + foobar; 9 | } 10 | 11 | } 12 | -------------------------------------------------------------------------------- /Chapter10/10_inversify_express_utils/entities/movie.ts: -------------------------------------------------------------------------------- 1 | import { 2 | Entity, 3 | PrimaryGeneratedColumn, 4 | Column 5 | } from "typeorm"; 6 | 7 | @Entity() 8 | export class Movie { 9 | @PrimaryGeneratedColumn() 10 | public id!: number; 11 | @Column() 12 | public title!: string; 13 | @Column() 14 | public year!: number; 15 | } 16 | -------------------------------------------------------------------------------- /Chapter07/03_function_types.ts: -------------------------------------------------------------------------------- 1 | namespace function_types_demo { 2 | 3 | function isNull(a: T|null) { 4 | return (a === null); 5 | } 6 | 7 | function add(a: number, b: number) { 8 | return a + b; 9 | } 10 | 11 | function addMany(...numbers: number[]) { 12 | numbers.reduce((p, c) => p + c, 0); 13 | } 14 | 15 | } 16 | -------------------------------------------------------------------------------- /Chapter11/tsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "compilerOptions": { 3 | "target": "es5", 4 | "module": "commonjs", 5 | "lib": ["es2015", "dom"], 6 | "jsx": "react", 7 | "strict": true, 8 | "esModuleInterop": true, 9 | "experimentalDecorators": true, 10 | "emitDecoratorMetadata": true 11 | }, 12 | "exclude": [ 13 | "backend" 14 | ] 15 | } 16 | -------------------------------------------------------------------------------- /Chapter12/tsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "compilerOptions": { 3 | "target": "es5", 4 | "module": "commonjs", 5 | "lib": ["es2015", "dom"], 6 | "jsx": "react", 7 | "strict": true, 8 | "esModuleInterop": true, 9 | "experimentalDecorators": true, 10 | "emitDecoratorMetadata": true 11 | }, 12 | "exclude": [ 13 | "backend" 14 | ] 15 | } 16 | -------------------------------------------------------------------------------- /Chapter12/web/frontend/components/error.component.ts: -------------------------------------------------------------------------------- 1 | import { Component, Input } from "@angular/core"; 2 | 3 | @Component({ 4 | selector: "app-error", 5 | template: ` 6 | ` 9 | }) 10 | export class ErrorComponent { 11 | @Input() public msg!: string; 12 | } 13 | -------------------------------------------------------------------------------- /Chapter02/13_literal_types.ts: -------------------------------------------------------------------------------- 1 | namespace literal_types_demo { 2 | 3 | let five1 = 5; // number 4 | let falsy1 = false; // boolean 5 | let shape1 = "rectangle"; // string 6 | 7 | const five2 = 5; // 5 8 | const falsy2 = false; // false 9 | const shape2 = "rectangle"; // rectangle 10 | 11 | type ShapeKind = "square" | "rectangle" | "circle"; 12 | 13 | } 14 | -------------------------------------------------------------------------------- /Chapter14/02_testing/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | Calculator 7 | 8 | 9 |
10 | 11 | 12 | 13 | -------------------------------------------------------------------------------- /Chapter09/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | Calculator 7 | 8 | 9 |
10 | 11 | 12 | 13 | -------------------------------------------------------------------------------- /Chapter11/web/frontend/config/ioc.ts: -------------------------------------------------------------------------------- 1 | import { Container } from "inversify"; 2 | import getDecorators from "inversify-inject-decorators"; 3 | import { makeProvideDecorator } from "inversify-binding-decorators"; 4 | 5 | const container = new Container(); 6 | const { lazyInject } = getDecorators(container); 7 | const provide = makeProvideDecorator(container); 8 | 9 | export { lazyInject, provide }; 10 | -------------------------------------------------------------------------------- /Chapter03/01_function_declaration.ts: -------------------------------------------------------------------------------- 1 | namespace function_declaration_demo { 2 | 3 | console.log(greetNamed("John")); // OK 4 | console.log(greetUnnamed("John")); // Error 5 | 6 | function greetNamed(name: string): string { 7 | return `Hi! ${name}`; 8 | } 9 | 10 | let greetUnnamed = function(name: string): string { 11 | return `Hi! ${name}`; 12 | }; 13 | 14 | } 15 | -------------------------------------------------------------------------------- /Chapter11/web/backend/entities/movie.ts: -------------------------------------------------------------------------------- 1 | import { Column, Entity, PrimaryGeneratedColumn } from "typeorm"; 2 | import { MovieInterface } from "../../universal/entities/movie"; 3 | 4 | @Entity() 5 | export class Movie implements MovieInterface { 6 | @PrimaryGeneratedColumn() 7 | public id!: number; 8 | @Column() 9 | public title!: string; 10 | @Column() 11 | public year!: number; 12 | } 13 | -------------------------------------------------------------------------------- /Chapter12/web/backend/entities/movie.ts: -------------------------------------------------------------------------------- 1 | import { Column, Entity, PrimaryGeneratedColumn } from "typeorm"; 2 | import { MovieInterface } from "../../universal/entities/movie"; 3 | 4 | @Entity() 5 | export class Movie implements MovieInterface { 6 | @PrimaryGeneratedColumn() 7 | public id!: number; 8 | @Column() 9 | public title!: string; 10 | @Column() 11 | public year!: number; 12 | } 13 | -------------------------------------------------------------------------------- /Chapter11/web/backend/entities/actor.ts: -------------------------------------------------------------------------------- 1 | import { Column, Entity, PrimaryGeneratedColumn } from "typeorm"; 2 | import { ActorInterface } from "../../universal/entities/actor"; 3 | 4 | @Entity() 5 | export class Actor implements ActorInterface { 6 | @PrimaryGeneratedColumn() 7 | public id!: number; 8 | @Column() 9 | public name!: string; 10 | @Column() 11 | public yearBorn!: number; 12 | } 13 | -------------------------------------------------------------------------------- /Chapter12/web/backend/entities/actor.ts: -------------------------------------------------------------------------------- 1 | import { Column, Entity, PrimaryGeneratedColumn } from "typeorm"; 2 | import { ActorInterface } from "../../universal/entities/actor"; 3 | 4 | @Entity() 5 | export class Actor implements ActorInterface { 6 | @PrimaryGeneratedColumn() 7 | public id!: number; 8 | @Column() 9 | public name!: string; 10 | @Column() 11 | public yearBorn!: number; 12 | } 13 | -------------------------------------------------------------------------------- /Chapter12/web/frontend/main.ts: -------------------------------------------------------------------------------- 1 | import { platformBrowserDynamic } from "@angular/platform-browser-dynamic"; 2 | // import { enableProdMode } from "@angular/core"; 3 | import { AppModule } from "./app.module"; 4 | 5 | // if (environment.production) { 6 | // enableProdMode(); 7 | // } 8 | 9 | platformBrowserDynamic().bootstrapModule(AppModule).catch((err) => { 10 | console.error(err); // tslint:disable-line 11 | }); 12 | -------------------------------------------------------------------------------- /Chapter10/05_http_hello.ts: -------------------------------------------------------------------------------- 1 | import * as http from "http"; 2 | 3 | const hostname = "127.0.0.1"; 4 | const port = 3000; 5 | 6 | const server = http.createServer((req, res) => { 7 | res.statusCode = 200; 8 | res.setHeader("Content-Types", "text/plain"); 9 | res.end("Hello world!"); 10 | }); 11 | 12 | server.listen(port, hostname, () => { 13 | console.log(`Server running at http://${hostname}:${port}/`); 14 | }); 15 | -------------------------------------------------------------------------------- /Chapter11/web/frontend/components/error_msg_component.tsx: -------------------------------------------------------------------------------- 1 | import * as React from "react"; 2 | 3 | interface ErrorMsgProps { 4 | msg: string; 5 | } 6 | 7 | export class ErrorMsg extends React.Component { 8 | public render() { 9 | return ( 10 |
11 | {this.props.msg} 12 |
13 | ); 14 | } 15 | } 16 | -------------------------------------------------------------------------------- /Chapter03/08_specialized_overloading.ts: -------------------------------------------------------------------------------- 1 | namespace specialized_overloading { 2 | 3 | interface Document { 4 | createElement(tagName: "div"): HTMLDivElement; // specialized 5 | createElement(tagName: "span"): HTMLSpanElement; // specialized 6 | createElement(tagName: "canvas"): HTMLCanvasElement; // specialized 7 | createElement(tagName: string): HTMLElement; // non-specialized 8 | } 9 | 10 | } 11 | -------------------------------------------------------------------------------- /Chapter09/src/main_server.ts: -------------------------------------------------------------------------------- 1 | import chalk from "chalk"; 2 | import { Calculator } from "./calculator"; 3 | 4 | const calculator = new Calculator(); 5 | 6 | const addResult = calculator.calculate("add", 2, 3); 7 | 8 | console.log(chalk.green(`2 + 3 = ${addResult}`)); // tslint:disable-line 9 | 10 | const powResult = calculator.calculate("pow", 2, 3); 11 | 12 | console.log(chalk.green(`2 + 3 = ${powResult}`)); // tslint:disable-line 13 | -------------------------------------------------------------------------------- /Chapter11/web/frontend/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | MobX example 7 | 8 | 9 | 10 |
Loading...
11 | 12 | 13 | 14 | -------------------------------------------------------------------------------- /Chapter14/02_testing/src/frontend/main.tsx: -------------------------------------------------------------------------------- 1 | import * as React from "react"; 2 | import * as ReactDOM from "react-dom"; 3 | import { Calculator } from "./calculator_component"; 4 | import "./main.scss"; 5 | import { MathClient } from "./math_client"; 6 | 7 | ReactDOM.render( 8 | ( 9 |
10 | 11 |
12 | ), 13 | document.querySelector("#main") 14 | ); 15 | -------------------------------------------------------------------------------- /Chapter14/02_testing/src/backend/interfaces.ts: -------------------------------------------------------------------------------- 1 | export interface MathInterface { 2 | PI: number; 3 | pow(base: number, exponent: number): number; 4 | powAsync(base: number, exponent: number): Promise; 5 | powAsyncSlow(base: number, exponent: number): Promise; 6 | powAsyncReallySlow(base: number, exponent: number): Promise; 7 | powAsyncTooSlow(base: number, exponent: number): Promise; 8 | bad(foo: any): void; 9 | } 10 | -------------------------------------------------------------------------------- /Chapter09/src/main_browser.ts: -------------------------------------------------------------------------------- 1 | import { Calculator } from "./calculator"; 2 | import "./main.scss"; 3 | 4 | const calculator = new Calculator(); 5 | 6 | const addResult = calculator.calculate("add", 2, 3); 7 | const powResult = calculator.calculate("pow", 2, 3); 8 | 9 | const $main = document.querySelector("#main"); 10 | 11 | if ($main) { 12 | $main.innerHTML = ` 13 |

2 + 3 = ${addResult}

14 |

2 + 3 = ${powResult}

15 | `; 16 | } 17 | -------------------------------------------------------------------------------- /Chapter10/readme.md: -------------------------------------------------------------------------------- 1 | # Chapter 10 2 | 3 | ``` 4 | cd cd chapters/chapter_10 5 | npm install 6 | ts-node 01_callback.ts 7 | ts-node 02_promisify.ts 8 | ts-node 03_file_system.ts 9 | ts-node 04_database.ts 10 | ts-node 05_http_hello.ts 11 | ts-node 06_express_hello.ts 12 | ts-node 07_express_routing.ts 13 | ts-node 08_express_middleware.ts 14 | ``` 15 | 16 | ``` 17 | ts-node 09_mvc/index.ts 18 | ``` 19 | 20 | ``` 21 | ts-node 10_inversify_express_utils/index.ts 22 | ``` 23 | -------------------------------------------------------------------------------- /Chapter01/02_variables.ts: -------------------------------------------------------------------------------- 1 | namespace type_inference_variables_demo { 2 | 3 | let testVar1; // variable is declared but not initialized 4 | console.log(testVar1); // shows undefined 5 | console.log(typeof testVar1); // shows undefined 6 | 7 | let testVar2 = null; // variable is declared and null is assigned as value 8 | console.log(testVar2); // shows null 9 | console.log(typeof testVar2); // shows object 10 | 11 | } 12 | -------------------------------------------------------------------------------- /Chapter02/20_index_signature.ts: -------------------------------------------------------------------------------- 1 | namespace index_signature_demo { 2 | 3 | let foo1: any = {}; 4 | foo1.hello = "World"; 5 | console.log(foo1.hello); // World 6 | 7 | let foo2: any = {}; 8 | foo2["hello"] = "World"; 9 | console.log(foo2["hello"]); // World 10 | 11 | interface StringArray { 12 | [index: number]: string; 13 | } 14 | 15 | let myArray: StringArray = ["Bob", "Fred"]; 16 | let myStr: string = myArray[0]; 17 | 18 | } 19 | -------------------------------------------------------------------------------- /Chapter03/02_function_types.ts: -------------------------------------------------------------------------------- 1 | namespace function_types_demo { 2 | 3 | function greetNamed(name?: string): string { 4 | return `Hi! ${name}`; 5 | } 6 | 7 | let greetUnnamed1: (name: string) => string; 8 | 9 | greetUnnamed1 = function(name: string): string { 10 | return `Hi! ${name}`; 11 | }; 12 | 13 | let greetUnnamed2: (name: string) => string = function(name: string): string { 14 | return `Hi! ${name}`; 15 | }; 16 | 17 | } 18 | -------------------------------------------------------------------------------- /Chapter04/15_interfaces.ts: -------------------------------------------------------------------------------- 1 | namespace interfaces_demo { 2 | 3 | interface Weapon { 4 | tryHit(fromDistance: number): boolean; 5 | } 6 | 7 | class Katana implements Weapon { 8 | public tryHit(fromDistance: number) { 9 | return fromDistance <= 2; 10 | } 11 | } 12 | 13 | class Shuriken implements Weapon { 14 | public tryHit(fromDistance: number) { 15 | return fromDistance <= 15; 16 | } 17 | } 18 | 19 | } 20 | -------------------------------------------------------------------------------- /Chapter12/web/frontend/components/loading.component.ts: -------------------------------------------------------------------------------- 1 | import { Component } from "@angular/core"; 2 | 3 | @Component({ 4 | selector: "app-loading", 5 | template: ` 6 |

7 |
12 |
13 |
` 14 | }) 15 | export class LoadingComponent { 16 | } 17 | -------------------------------------------------------------------------------- /Chapter04/05_class_expressions.ts: -------------------------------------------------------------------------------- 1 | namespace class_expressions { 2 | 3 | const Person = class { 4 | public constructor( 5 | public name: string, 6 | public surname: string, 7 | public email: string 8 | ) {} 9 | public greet() { 10 | console.log("Hi!"); 11 | } 12 | }; 13 | 14 | const person = new Person( 15 | "Remo", 16 | "Jansen", 17 | "remo.jansen@wolksoftware.com" 18 | ); 19 | 20 | } 21 | -------------------------------------------------------------------------------- /Chapter02/02_type_annotations.ts: -------------------------------------------------------------------------------- 1 | // Don't forget to disable strictNullChecks or strict in tsconfig.json 2 | namespace type_annotations_demo { 3 | 4 | // (a: any, b: any) => any; 5 | function add(a, b) { 6 | return a + b; 7 | } 8 | 9 | add(2, 3); // 5 10 | add("2", 3); // "23" 11 | 12 | // (a: number, b: number) => number; 13 | function annotatedAdd(a: number, b: number): number { 14 | return a + b; 15 | } 16 | 17 | annotatedAdd(2, 3); // OK 18 | annotatedAdd("2", 3); // Error 19 | 20 | } -------------------------------------------------------------------------------- /Chapter02/01_type_inference.ts: -------------------------------------------------------------------------------- 1 | // Don't forget to disable strictNullChecks or strict in tsconfig.json 2 | namespace type_inference_demo { 3 | 4 | // number 5 | let myVariable1 = 3; 6 | 7 | // string 8 | let myVariable2 = "Hello"; 9 | 10 | // { name: string; surname: string; age: number; } 11 | let myVariable3 = { 12 | name: "Remo", 13 | surname: "Jansen", 14 | age: 28 15 | }; 16 | 17 | // (a: any, b: any) => any 18 | function add(a, b) { 19 | return a + b; 20 | } 21 | 22 | } 23 | -------------------------------------------------------------------------------- /Chapter11/web/frontend/index.tsx: -------------------------------------------------------------------------------- 1 | import "reflect-metadata"; 2 | import * as React from "react"; 3 | import * as ReacDOM from "react-dom"; 4 | import "../../node_modules/bootstrap/scss/bootstrap.scss"; 5 | import { Layout } from "./config/layout"; 6 | 7 | const selector = "#root"; 8 | const $element = document.querySelector(selector); 9 | 10 | if (!$element) { 11 | throw new Error(`Node ${selector} not found!`); 12 | } else { 13 | ReacDOM.render( 14 | , 15 | $element 16 | ); 17 | } 18 | -------------------------------------------------------------------------------- /Chapter08/05_decorator_options.ts: -------------------------------------------------------------------------------- 1 | namespace decorator_option { 2 | 3 | function logClass(option: string) { 4 | return function(target: any) { 5 | // class decorator logic goes here 6 | // we have access to the decorator 7 | // parameters and target 8 | console.log(`Decorating ${target.name} with ${option}`); 9 | }; 10 | } 11 | 12 | @logClass("some option") 13 | class Person {} 14 | 15 | // Decorating Person with some option 16 | 17 | } 18 | -------------------------------------------------------------------------------- /Chapter02/23_generic_types.ts: -------------------------------------------------------------------------------- 1 | namespace generic_types_demo { 2 | 3 | function deserialize(json: string): T { 4 | return JSON.parse(json) as T; 5 | } 6 | 7 | interface User { 8 | name: string; 9 | age: number; 10 | } 11 | 12 | let user = deserialize(`{"name":"Remo","age":28}`); 13 | 14 | interface Rectangle { 15 | width: number; 16 | height: number; 17 | } 18 | 19 | let rectangle = deserialize(`{"width":5,"height":8}`); 20 | 21 | } 22 | -------------------------------------------------------------------------------- /Chapter04/04_parameter_properties.ts: -------------------------------------------------------------------------------- 1 | namespace parameter_properties_demo_1 { 2 | 3 | class Vector { 4 | private x: number; 5 | private y: number; 6 | public constructor(x: number, y: number) { 7 | this.x = x; 8 | this.y = y; 9 | } 10 | } 11 | 12 | } 13 | 14 | namespace parameter_properties_demo_1 { 15 | 16 | class Vector { 17 | public constructor( 18 | private x: number, 19 | private y: number 20 | ) {} 21 | } 22 | 23 | } 24 | -------------------------------------------------------------------------------- /Chapter11/web/frontend/components/loading_component.tsx: -------------------------------------------------------------------------------- 1 | import * as React from "react"; 2 | 3 | export class Loading extends React.Component { 4 | public render() { 5 | return ( 6 |
7 |
12 |
13 |
14 | ); 15 | } 16 | } 17 | -------------------------------------------------------------------------------- /Chapter09/test/add.test.ts: -------------------------------------------------------------------------------- 1 | import { expect } from "chai"; 2 | import { add } from "../src/operations/add"; 3 | 4 | describe("Operation: add", () => { 5 | 6 | it ("Should be able to calculate operation", () => { 7 | const result = add(2, 3); 8 | expect(result).to.eql(5); 9 | }); 10 | 11 | it ("Should throw if an invalid argument is provided", () => { 12 | const a: any = "2"; 13 | const b: any = 3; 14 | const throws = () => add(a, b); 15 | expect(throws).to.throw(); 16 | }); 17 | 18 | }); 19 | -------------------------------------------------------------------------------- /Chapter09/test/pow.test.ts: -------------------------------------------------------------------------------- 1 | import { expect } from "chai"; 2 | import { pow } from "../src/operations/pow"; 3 | 4 | describe("Operation: pow", () => { 5 | 6 | it ("Should be able to calculate operation", () => { 7 | const result = pow(2, 3); 8 | expect(result).to.eql(8); 9 | }); 10 | 11 | it ("Should throw if an invalid argument is provided", () => { 12 | const a: any = "2"; 13 | const b: any = 3; 14 | const throws = () => pow(a, b); 15 | expect(throws).to.throw(); 16 | }); 17 | 18 | }); 19 | -------------------------------------------------------------------------------- /Chapter09/test/validation.test.ts: -------------------------------------------------------------------------------- 1 | import { expect } from "chai"; 2 | import { isNumber } from "../src/operations/validation"; 3 | 4 | describe("isNumber", () => { 5 | 6 | it ("Should be able to add calculate operation", () => { 7 | const works = () => isNumber(2); 8 | expect(works).not.to.throw(); 9 | }); 10 | 11 | it ("Should throw if an invalid argument is provided", () => { 12 | const a: any = "2"; 13 | const throws = () => isNumber(a); 14 | expect(throws).to.throw(); 15 | }); 16 | 17 | }); 18 | -------------------------------------------------------------------------------- /Chapter12/web/frontend/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | Angular example 7 | 8 | 9 | 10 | 11 | 12 | Loading... 13 | 14 | 15 | 16 | 17 | -------------------------------------------------------------------------------- /tsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "compilerOptions": { 3 | "target": "es6", 4 | "module": "commonjs", 5 | "types": [ 6 | "reflect-metadata" 7 | ], 8 | "lib": [ 9 | "es2015.promise", 10 | "dom", 11 | "es5", 12 | "es2015.generator", 13 | "es2015.iterable", 14 | "esnext.asynciterable", 15 | "es6", 16 | "es2015.symbol" 17 | ], 18 | "downlevelIteration": true, 19 | "strict": true, 20 | "experimentalDecorators": true, 21 | "emitDecoratorMetadata": true, 22 | "jsx": "react" 23 | } 24 | } -------------------------------------------------------------------------------- /Chapter01/05_spread_operator.ts: -------------------------------------------------------------------------------- 1 | namespace spread_operator_demo { 2 | 3 | let originalArr1 = [ 1, 2, 3]; 4 | let originalArr2 = [ 4, 5, 6]; 5 | let copyArr = [...originalArr1]; 6 | let mergedArr = [...originalArr1, ...originalArr2]; 7 | let newObjArr = [...originalArr1, 7, 8]; 8 | 9 | let originalObj1 = {a: 1, b: 2, c: 3}; 10 | let originalObj2 = {d: 4, e: 5, f: 6}; 11 | let copyObj = {...originalObj1}; 12 | let mergedObj = {...originalObj1, ...originalObj2}; 13 | let newObjObj = {... originalObj1, g: 7, h: 8}; 14 | 15 | } 16 | -------------------------------------------------------------------------------- /Chapter10/02_promisify.ts: -------------------------------------------------------------------------------- 1 | import { readFile } from "fs"; 2 | import { promisify } from "util"; 3 | 4 | const readFileAsync = promisify(readFile); 5 | 6 | (async () => { 7 | const buffer = await readFileAsync("./hello.txt", "utf-8"); 8 | console.log(buffer.toString()); 9 | })(); 10 | 11 | async function readJson(fileName: string) { 12 | try { 13 | const buffer = await readFileAsync(fileName, "utf-8"); 14 | const parsed = JSON.parse(buffer.toString()); 15 | return parsed; 16 | } catch (err) { 17 | return err; 18 | } 19 | } 20 | -------------------------------------------------------------------------------- /Chapter12/web/frontend/pages/homepage.module.ts: -------------------------------------------------------------------------------- 1 | import { NgModule } from "@angular/core"; 2 | import { CommonModule } from "@angular/common"; 3 | import { RouterModule } from "@angular/router"; 4 | import { HomePageComponent } from "./homepage.component"; 5 | import { ComponentsModule } from "../components/components.module"; 6 | 7 | @NgModule({ 8 | declarations: [ 9 | HomePageComponent 10 | ], 11 | exports: [ 12 | HomePageComponent 13 | ], 14 | imports: [CommonModule, RouterModule, ComponentsModule] 15 | }) 16 | export class HomePageModule {} 17 | -------------------------------------------------------------------------------- /Chapter02/12_control_flow_analysis.ts: -------------------------------------------------------------------------------- 1 | namespace control_flow_analysis_demo { 2 | 3 | function increment( 4 | incrementBy: number, valueOrValues: number | number[] 5 | ) { 6 | if (Array.isArray(valueOrValues)) { 7 | // values must be an array of number 8 | return valueOrValues.map((value) => value + incrementBy); 9 | } else { 10 | // values is a number 11 | return valueOrValues + incrementBy; 12 | } 13 | } 14 | 15 | increment(2, 2); // 4 16 | increment(2, [2, 4, 6]); // [4, 6, 8] 17 | 18 | } 19 | -------------------------------------------------------------------------------- /Chapter02/21_local_types.ts: -------------------------------------------------------------------------------- 1 | namespace local_type_demo { 2 | 3 | interface Person { 4 | name: string; 5 | age: number; 6 | } 7 | 8 | function makePerson(employeeName: string, employeeAge: number): Person { 9 | 10 | // Local type 11 | class Employee implements Person { 12 | constructor( 13 | public name: string, 14 | public age: number 15 | ) {} 16 | } 17 | 18 | return new Employee(employeeName, employeeAge); 19 | 20 | } 21 | 22 | let user = makePerson("Remo", 28); 23 | 24 | } 25 | -------------------------------------------------------------------------------- /Chapter15/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "chapter_12", 3 | "version": "1.0.0", 4 | "description": "", 5 | "main": "index.js", 6 | "scripts": { 7 | "test": "echo \"Error: no test specified\" && exit 1" 8 | }, 9 | "keywords": [], 10 | "author": "", 11 | "license": "MIT", 12 | "dependencies": { 13 | "@types/lodash": "4.14.98", 14 | "@types/node": "9.4.0", 15 | "@types/request": "2.47.0", 16 | "chalk": "2.3.0", 17 | "lodash": "4.17.4", 18 | "request": "2.83.0", 19 | "save": "2.3.2", 20 | "ts-simple-ast": "10.3.0", 21 | "typescript": "^2.8.1" 22 | } 23 | } 24 | -------------------------------------------------------------------------------- /Chapter10/01_callback.ts: -------------------------------------------------------------------------------- 1 | import { readFile } from "fs"; 2 | 3 | readFile("./hello.txt", "utf-8", (err, buffer) => { 4 | console.log(buffer.toString()); 5 | }); 6 | 7 | function readJson( 8 | fileName: string, 9 | callback: (err: Error|null, json?: any) => void 10 | ) { 11 | readFile(fileName, "utf-8", (err, buffer) => { 12 | if (err) { 13 | callback(err); 14 | } 15 | try { 16 | const parsed = JSON.parse(buffer); 17 | callback(null, parsed); 18 | } catch (innerErr) { 19 | callback(err); 20 | } 21 | }); 22 | } 23 | -------------------------------------------------------------------------------- /Chapter13/01_exception.ts: -------------------------------------------------------------------------------- 1 | namespace CustomException { 2 | 3 | // More info at https://github.com/Microsoft/TypeScript-wiki/blob/master/Breaking-Changes.md#extending-built-ins-like-error-array-and-map-may-no-longer-work 4 | 5 | export class Exception extends Error { 6 | 7 | public constructor(public message: string) { 8 | super(message); 9 | // Set the prototype explicitly. 10 | Object.setPrototypeOf(this, Exception.prototype); 11 | } 12 | public sayHello() { 13 | return `hello ${this.message}`; 14 | } 15 | } 16 | 17 | } 18 | -------------------------------------------------------------------------------- /Chapter02/17_object_literals.ts: -------------------------------------------------------------------------------- 1 | namespace object_literal_demo { 2 | 3 | let person1 = { name: "Remo", age: 28 }; 4 | 5 | interface User { 6 | name: string; 7 | age: number; 8 | } 9 | 10 | let person2: User = { name: "Remo", age: 28 }; // OK 11 | let person3: User = { name: "Remo" }; // Error 12 | 13 | interface UserWithOptionalProperties { 14 | name: string; 15 | age?: number; 16 | } 17 | 18 | let person4: UserWithOptionalProperties = { name: "Remo", age: 28 }; // OK 19 | let person5: UserWithOptionalProperties = { name: "Remo" }; // OK 20 | 21 | } 22 | -------------------------------------------------------------------------------- /Chapter12/web/frontend/interfaces.ts: -------------------------------------------------------------------------------- 1 | import { ActorInterface } from "../universal/entities/actor"; 2 | import { MovieInterface } from "../universal/entities/movie"; 3 | 4 | export type Status = "idle" | "pending" | "error" | "done"; 5 | 6 | export interface ActorService { 7 | getAll(): Promise; 8 | create(actor: ActorInterface): Promise; 9 | delete(id: number): Promise; 10 | } 11 | 12 | export interface MovieService { 13 | getAll(): Promise; 14 | create(movie: MovieInterface): Promise; 15 | delete(id: number): Promise; 16 | } 17 | -------------------------------------------------------------------------------- /Chapter06/01_stack.ts: -------------------------------------------------------------------------------- 1 | namespace stack_demo { 2 | 3 | function foo(a: number): number { 4 | const value = 12; 5 | return value + a; 6 | } 7 | 8 | function bar(b: number): number { 9 | const value = 4; 10 | return foo(value * b); 11 | } 12 | 13 | bar(21); 14 | 15 | } 16 | 17 | namespace stack_demo_2 { 18 | 19 | function foo(a: number): number { 20 | const value = 12; 21 | return bar(value + a); 22 | } 23 | 24 | function bar(b: number): number { 25 | const value = 4; 26 | return foo(value * b); 27 | } 28 | 29 | bar(21); 30 | 31 | } 32 | -------------------------------------------------------------------------------- /Chapter09/gulpfile.js: -------------------------------------------------------------------------------- 1 | let gulp = require("gulp"); 2 | 3 | gulp.task("hello", function() { 4 | console.log("Hello Gulp!"); 5 | }); 6 | 7 | let tslint = require("tslint"); 8 | let gulpTslint = require("gulp-tslint"); 9 | 10 | gulp.task("lint", function() { 11 | 12 | let program = tslint.Linter.createProgram("./tsconfig.json"); 13 | 14 | return gulp.src([ 15 | "src/**/**.ts", 16 | "test/**/**.test.ts" 17 | ]) 18 | .pipe(gulpTslint({ 19 | formatter: "stylish", 20 | program 21 | })) 22 | .pipe(gulpTslint.report()); 23 | 24 | }); 25 | 26 | gulp.task("default", ["hello", "lint"]); 27 | -------------------------------------------------------------------------------- /Chapter14/02_testing/globals.js: -------------------------------------------------------------------------------- 1 | const chromedriver = require("chromedriver"); 2 | 3 | module.exports = { 4 | before: (done) => { 5 | chromedriver.start(); 6 | done(); 7 | }, 8 | after: (done) => { 9 | chromedriver.stop(); 10 | done(); 11 | }, 12 | reporter: function(results) { 13 | if ( 14 | (typeof(results.failed) === "undefined" || results.failed === 0) && 15 | (typeof(results.error) === "undefined" || results.error === 0) 16 | ) { 17 | process.exit(0); 18 | } else { 19 | process.exit(1); 20 | } 21 | } 22 | }; 23 | -------------------------------------------------------------------------------- /Chapter14/01_assertions/assertions.ts: -------------------------------------------------------------------------------- 1 | import { MathApi } from "./application"; 2 | 3 | const actual1 = MathApi.pow(3, 5); 4 | const expected1 = 243; 5 | const asertion1 = actual1 === expected1; 6 | 7 | if (asertion1 === false) { 8 | throw new Error( 9 | `Expected 'actual1' to be ${expected1} ` + 10 | `but got ${actual1}!` 11 | ); 12 | } 13 | 14 | const actual2 = MathApi.pow(5, 3); 15 | const expected2 = 125; 16 | const asertion2 = actual2 === expected2; 17 | 18 | if (asertion2 === false) { 19 | throw new Error( 20 | `Expected 'actual2' to be ${expected2} ` + 21 | `but got ${actual2}!` 22 | ); 23 | } 24 | -------------------------------------------------------------------------------- /Chapter02/16_enumerations.ts: -------------------------------------------------------------------------------- 1 | namespace enumerations_demo { 2 | 3 | enum DirectionEnum { 4 | Up, 5 | Down, 6 | Left, 7 | Right 8 | } 9 | 10 | type DirectionUnionType = 11 | "Up" 12 | | "Down" 13 | | "Left" 14 | | "Right"; 15 | 16 | function move(distance: number, direction: DirectionUnionType) { 17 | // ... 18 | } 19 | 20 | move(1, "Right"); // Okay 21 | move(1, "Righ"); // Error! 22 | 23 | enum DirectionStringEnum { 24 | Up = "Up", 25 | Down = "Down", 26 | Left = "Left", 27 | Right = "Right" 28 | } 29 | 30 | } 31 | -------------------------------------------------------------------------------- /Chapter05/03_multifile_namespace.ts: -------------------------------------------------------------------------------- 1 | namespace App { 2 | export namespace Validation { 3 | export class UserValidator { 4 | // ... 5 | } 6 | 7 | export class TalkValidator { 8 | // ... 9 | } 10 | } 11 | } 12 | 13 | // models is defined in the previous file 14 | const userModel = new App.Models.UserModel(); 15 | const talkModel = new App.Models.TalkModel(); 16 | const userValidator = new App.Validation.UserValidator(); 17 | const talkValidator = new App.Validation.TalkValidator(); 18 | 19 | import TalkValidator = App.Validation.TalkValidator; 20 | const talkValidator1 = new TalkValidator(); 21 | -------------------------------------------------------------------------------- /Chapter08/01_class_decorator.ts: -------------------------------------------------------------------------------- 1 | import { logClass } from "./decorators" 2 | 3 | namespace class_decorator_demo { 4 | 5 | @logClass 6 | class Person { 7 | 8 | public name: string; 9 | public surname: string; 10 | 11 | public constructor(name: string, surname: string) { 12 | this.name = name; 13 | this.surname = surname; 14 | } 15 | 16 | public saySomething(something: string): string { 17 | return `${this.name} ${this.surname} says: ${something}`; 18 | } 19 | 20 | } 21 | 22 | const person = new Person("Michael", "Jackson"); 23 | // New: Person 24 | 25 | } 26 | -------------------------------------------------------------------------------- /Chapter10/09_mvc/index.ts: -------------------------------------------------------------------------------- 1 | import "reflect-metadata"; 2 | import express from "express"; 3 | import * as bodyParser from "body-parser"; 4 | import { getDbConnection } from "./db"; 5 | import { movieRouter } from "./controllers/movie_controller"; 6 | 7 | (async () => { 8 | 9 | await getDbConnection(); 10 | 11 | const port = 3000; 12 | const app = express(); 13 | 14 | app.use(bodyParser.json()); 15 | app.use(bodyParser.urlencoded({ extended: true })); 16 | app.use("/api/v1/movies", movieRouter); 17 | 18 | app.listen(port, () => { 19 | console.log(`Server running at http://127.0.0.1:${port}/`) 20 | }); 21 | 22 | })(); 23 | -------------------------------------------------------------------------------- /Chapter02/07_nullable_types.ts: -------------------------------------------------------------------------------- 1 | // Don't forget to disable strictNullChecks or strict in tsconfig.json 2 | namespace nullable_types_demo { 3 | 4 | let name: string; 5 | name = "Remo"; // OK 6 | name = null; // OK 7 | name = undefined; // OK 8 | 9 | let age: number; 10 | age = 28; // OK 11 | age = null; // OK 12 | age = undefined; // OK 13 | 14 | let person: { name: string, age: number}; 15 | person = { name: "Remo", age: 28 }; // OK 16 | person = { name: null, age: null }; // OK 17 | person = { name: undefined, age: undefined }; // OK 18 | person = null; // OK 19 | person = undefined; // OK 20 | 21 | } 22 | -------------------------------------------------------------------------------- /Chapter01/09_interfaces.ts: -------------------------------------------------------------------------------- 1 | namespace interfaces_demo { 2 | 3 | interface LoggerInterface { 4 | log(arg: any): void; 5 | } 6 | 7 | class Logger implements LoggerInterface { 8 | public log(arg: any) { 9 | if (typeof console.log === "function") { 10 | console.log(arg); 11 | } else { 12 | console.log(arg); 13 | } 14 | } 15 | } 16 | 17 | interface UserInterface { 18 | name: string; 19 | password: string; 20 | } 21 | 22 | // Error property password is missing 23 | let user: UserInterface = { 24 | name: "" 25 | }; 26 | 27 | } 28 | -------------------------------------------------------------------------------- /Chapter02/26_lookup_types.ts: -------------------------------------------------------------------------------- 1 | namespace lookup_types_demo { 2 | 3 | function filterByProperty( 4 | property: K, entities: T[], value: T[K] 5 | ) { 6 | return entities.filter((e) => e[property] === value); 7 | } 8 | 9 | interface User { 10 | surname: string; 11 | age: number; 12 | } 13 | 14 | const users = [ 15 | { surname: "Smith", age: 28 }, 16 | { surname: "Johnson", age: 55 }, 17 | { surname: "Williams", age: 14 } 18 | ]; 19 | 20 | filterByProperty("age", users, 21); 21 | filterByProperty("surname", users, "Smith"); 22 | 23 | } 24 | -------------------------------------------------------------------------------- /Chapter14/02_testing/src/frontend/main.scss: -------------------------------------------------------------------------------- 1 | body { 2 | background-color: #eeeeee; 3 | } 4 | 5 | .btn { 6 | color: #ffffff; 7 | background-color: #333333; 8 | padding: 5px 15px; 9 | border-radius: 4px; 10 | } 11 | 12 | .error-msg { 13 | color: red; 14 | } 15 | 16 | .well { 17 | width: 60%; 18 | margin: auto; 19 | background-color: #ffffff; 20 | padding: 15px; 21 | border-radius: 4px; 22 | } 23 | 24 | .row { 25 | display: flex; 26 | } 27 | 28 | .row .col { 29 | float: left; 30 | width: 20%; 31 | } 32 | 33 | .form-group { 34 | padding-top: 5px; 35 | } 36 | 37 | .form-group label { 38 | margin-right: 5px; 39 | } -------------------------------------------------------------------------------- /Chapter02/08_non_nullable_types.ts: -------------------------------------------------------------------------------- 1 | // Don't forget to ENABLE strictNullChecks or strict in tsconfig.json 2 | namespace nullable_types_demo { 3 | 4 | let name: string; 5 | name = "Remo"; // OK 6 | name = null; // Error 7 | name = undefined; // Error 8 | 9 | let age: number; 10 | age = 28; // OK 11 | age = null; // Error 12 | age = undefined; // Error 13 | 14 | let person: { name: string, age: number}; 15 | person = { name: "Remo", age: 28 }; // OK 16 | person = { name: null, age: null }; // Error 17 | person = { name: undefined, age: undefined }; // Error 18 | person = null; // Error 19 | person = undefined; // Error 20 | 21 | } -------------------------------------------------------------------------------- /Chapter02/04_union_types.ts: -------------------------------------------------------------------------------- 1 | // Don"t forget to disable strictNullChecks or strict in tsconfig.json 2 | namespace union_types_demo { 3 | 4 | let path: string[]|string; 5 | path = "/temp/log.xml"; 6 | path = ["/temp/log.xml", "/temp/errors.xml"]; 7 | path = 1; // Error 8 | 9 | interface Supplier { 10 | orderItems(): void; 11 | getAddress(): void; 12 | } 13 | 14 | interface Customer { 15 | sellItems(): void; 16 | getAddress(): void; 17 | } 18 | 19 | declare let person: Supplier | Customer; 20 | person.getAddress(); // OK 21 | person.orderItems(); // Error 22 | person.sellItems(); // Error 23 | 24 | } 25 | -------------------------------------------------------------------------------- /Chapter03/12_arrow_functions.ts: -------------------------------------------------------------------------------- 1 | namespace arrow_functions { 2 | 3 | class Person { 4 | private _name: string; 5 | constructor(name: string) { 6 | this._name = name; 7 | } 8 | public greet() { 9 | console.log(`Hi! My name is ${this._name}`); 10 | } 11 | public greetDelay(time: number) { 12 | setTimeout(() => { 13 | console.log(`Hi! My name is ${this._name}`); // OK 14 | }, time); 15 | } 16 | } 17 | 18 | let person = new Person("Remo"); 19 | person.greet(); // "Hi! My name is Remo" 20 | person.greetDelay(1000); // "Hi! My name is Remo" 21 | 22 | } 23 | -------------------------------------------------------------------------------- /Chapter10/10_inversify_express_utils/inversify.config.ts: -------------------------------------------------------------------------------- 1 | import { AsyncContainerModule } from "inversify"; 2 | import { Repository, Connection } from "typeorm"; 3 | import { Movie } from "./entities/movie"; 4 | import { getDbConnection } from "./db"; 5 | import { getRepository } from "./repositories/movie_repository"; 6 | import { TYPE } from "./constants/types"; 7 | 8 | export const bindings = new AsyncContainerModule(async (bind) => { 9 | 10 | await getDbConnection(); 11 | await require("./controllers/movie_controller"); 12 | 13 | bind>(TYPE.MovieRepository).toDynamicValue(() => { 14 | return getRepository(); 15 | }).inRequestScope(); 16 | 17 | }); 18 | -------------------------------------------------------------------------------- /Chapter14/02_testing/jsdom.js: -------------------------------------------------------------------------------- 1 | const { JSDOM } = require("jsdom" ); 2 | const jsdom = new JSDOM(" " ); 3 | const { window } = jsdom; 4 | 5 | function copyProps(src, target) { 6 | const props = Object.getOwnPropertyNames(src) 7 | .filter(prop => typeof target[prop] === " undefined" ) 8 | .reduce((result, prop) => ({ 9 | ...result, 10 | [prop]: Object.getOwnPropertyDescriptor(src, prop), 11 | }), {}); 12 | Object.defineProperties(target, props); 13 | } 14 | 15 | global.window = window; 16 | global.document = window.document; 17 | global.navigator = { 18 | userAgent: " node.js" , 19 | }; 20 | 21 | copyProps(window, global); 22 | -------------------------------------------------------------------------------- /Chapter05/04_periods.ts: -------------------------------------------------------------------------------- 1 | namespace period_demo { 2 | 3 | namespace App.Models { 4 | export class UserModel { 5 | // ... 6 | } 7 | export class TalkModel { 8 | // ... 9 | } 10 | } 11 | namespace App.Validation { 12 | export class UserValidator { 13 | // ... 14 | } 15 | export class TalkValidator { 16 | // ... 17 | } 18 | } 19 | 20 | const userModel = new App.Models.UserModel(); 21 | const talkModel = new App.Models.TalkModel(); 22 | const userValidator = new App.Validation.UserValidator(); 23 | const talkValidator = new App.Validation.TalkValidator(); 24 | 25 | } 26 | -------------------------------------------------------------------------------- /Chapter10/07_express_routing.ts: -------------------------------------------------------------------------------- 1 | import express from "express"; 2 | 3 | const moviesRouter = express.Router(); 4 | 5 | // URL "/api/v1/movies" + "/" 6 | moviesRouter.get("/", (req, res) => { 7 | res.send("Hello from movies!"); 8 | }); 9 | 10 | const directorsRouter = express.Router(); 11 | 12 | // URL "/api/v1/directors" + "/" 13 | directorsRouter.get("/", (req, res) => { 14 | res.send("Hello from directors!"); 15 | }); 16 | 17 | const port = 3000; 18 | const app = express(); 19 | 20 | app.use("/api/v1/movies", moviesRouter); 21 | app.use("/api/v1/directors", directorsRouter); 22 | 23 | app.listen(port, () => { 24 | console.log(`Server running at http://127.0.0.1:${port}/`); 25 | }); 26 | -------------------------------------------------------------------------------- /Chapter07/14_ramda.ts: -------------------------------------------------------------------------------- 1 | import * as R from "ramda"; 2 | 3 | // npm install --save ramda 4 | // npm install --save-dev @types/ramda 5 | 6 | namespace ramda_demo_1 { 7 | 8 | const trim = (s: string) => s.trim(); 9 | const capitalize = (s: string) => s.toUpperCase(); 10 | const trimAndCapitalize = R.compose(trim, capitalize); 11 | 12 | const replace = (s: string, f: string, r: string) => 13 | s.split(f).join(r); 14 | 15 | const curriedReplace = R.curry(replace); 16 | 17 | const trimCapitalizeAndReplace = R.compose( 18 | trimAndCapitalize, 19 | curriedReplace("/")("-") 20 | ); 21 | 22 | trimCapitalizeAndReplace(" 13/feb/1989 "); // "13-FEB-1989" 23 | 24 | } 25 | -------------------------------------------------------------------------------- /Chapter12/web/frontend/app.module.ts: -------------------------------------------------------------------------------- 1 | import { NgModule } from "@angular/core"; 2 | import { BrowserModule } from "@angular/platform-browser"; 3 | import { CommonModule } from "@angular/common"; 4 | import { AppRoutingModule } from "./app-routing.module"; 5 | import { AppComponent } from "./app.component"; 6 | import { LayoutModule } from "./config/layout.module"; 7 | import "../../node_modules/bootstrap/scss/bootstrap.scss"; 8 | import "./app.scss"; 9 | 10 | @NgModule({ 11 | bootstrap: [AppComponent], 12 | declarations: [AppComponent], 13 | imports: [ 14 | BrowserModule, 15 | CommonModule, 16 | AppRoutingModule, 17 | LayoutModule 18 | ] 19 | }) 20 | export class AppModule { 21 | } 22 | -------------------------------------------------------------------------------- /Chapter03/03_trailing_comas.ts: -------------------------------------------------------------------------------- 1 | namespace trailing_comas { 2 | 3 | function greetWithoutTralingComans( 4 | name: string 5 | ): string { 6 | return `Hi! ${name}`; 7 | } 8 | 9 | function updatedGreetWithoutTralingComans( 10 | name: string 11 | surname: string, // Error 12 | ): string { 13 | return `Hi! ${name} ${surname}`; 14 | } 15 | 16 | function greetWithTralingComans( 17 | name: string, 18 | ): string { 19 | return `Hi! ${name}`; 20 | } 21 | 22 | function updatedGreetWithTralingComans( 23 | name: string, 24 | surname: string, 25 | ): string { 26 | return `Hi! ${name} ${surname}`; 27 | } 28 | 29 | } 30 | -------------------------------------------------------------------------------- /Chapter12/web/frontend/config/layout.component.ts: -------------------------------------------------------------------------------- 1 | import { Component, OnInit } from "@angular/core"; 2 | import { Route } from "../components/header.component"; 3 | 4 | @Component({ 5 | selector: "app-layout", 6 | template: ` 7 |
8 | 14 |
15 | 16 |
17 |
18 | ` 19 | }) 20 | export class LayoutComponent { 21 | public appRoutes: Route[] = [ 22 | { label: "Movies", path: "movies" }, 23 | { label: "Actors", path: "actors" } 24 | ]; 25 | } 26 | -------------------------------------------------------------------------------- /Chapter01/08_classes.ts: -------------------------------------------------------------------------------- 1 | namespace classes_demo { 2 | 3 | class Character { 4 | public fullname: string; 5 | public constructor(firstname: string, lastname: string) { 6 | this.fullname = `${firstname} ${lastname}`; 7 | } 8 | public greet(name?: string) { 9 | if (name) { 10 | return `Hi! ${name}! my name is ${this.fullname}`; 11 | } else { 12 | return `Hi! my name is ${this.fullname}`; 13 | } 14 | } 15 | } 16 | 17 | let spark = new Character("Jacob", "Keyes"); 18 | let msg = spark.greet(); 19 | console.log(msg); // "Hi! my name is Jacob Keyes" 20 | 21 | let msg1 = spark.greet("Dr. Halsey"); 22 | console.log(msg1); // "Hi! Dr. Halsey! my name is Jacob Keyes" 23 | 24 | } 25 | -------------------------------------------------------------------------------- /Chapter12/web/frontend/pages/actorspage.module.ts: -------------------------------------------------------------------------------- 1 | import { NgModule } from "@angular/core"; 2 | import { ActorsPageComponent } from "./actorspage.component"; 3 | import { CommonModule } from "@angular/common"; 4 | import { ComponentsModule } from "../components/components.module"; 5 | import { ActorService } from "../services/actor_service"; 6 | import { ACTOR_SERVICE } from "../config/types"; 7 | 8 | @NgModule({ 9 | declarations: [ 10 | ActorsPageComponent 11 | ], 12 | exports: [ 13 | ActorsPageComponent 14 | ], 15 | imports: [CommonModule, ComponentsModule], 16 | providers: [ 17 | { provide: ACTOR_SERVICE, useClass: ActorService } 18 | ] 19 | }) 20 | export class ActorsPageModule { 21 | } 22 | -------------------------------------------------------------------------------- /Chapter12/web/frontend/pages/moviespage.module.ts: -------------------------------------------------------------------------------- 1 | import { NgModule } from "@angular/core"; 2 | import { CommonModule } from "@angular/common"; 3 | import { MoviesPageComponent } from "./moviespage.component"; 4 | import { ComponentsModule } from "../components/components.module"; 5 | import { MovieService } from "../services/movie_service"; 6 | import { MOVIE_SERVICE } from "../config/types"; 7 | 8 | @NgModule({ 9 | declarations: [ 10 | MoviesPageComponent 11 | ], 12 | exports: [ 13 | MoviesPageComponent 14 | ], 15 | imports: [CommonModule, ComponentsModule], 16 | providers: [ 17 | { provide: MOVIE_SERVICE, useClass: MovieService } 18 | ] 19 | }) 20 | export class MoviesPageModule { 21 | } 22 | -------------------------------------------------------------------------------- /Chapter02/03_structural_type_system.ts: -------------------------------------------------------------------------------- 1 | // Don't forget to disable strictNullChecks or strict in tsconfig.json 2 | namespace structural_type_system_demo { 3 | 4 | interface Person { 5 | name: string; 6 | surname: string; 7 | } 8 | 9 | function getFullName(person: Person) { 10 | return `${person.name} ${person.surname}`; 11 | } 12 | 13 | class Employer { 14 | constructor( 15 | public name: string, 16 | public surname: string 17 | ) {} 18 | } 19 | 20 | getFullName(new Employer("remo", "jansen")); // OK 21 | 22 | const p1 = { name: "remo", surname: "jansen" }; 23 | getFullName(p1); // OK 24 | 25 | const p2 = { name: "remo", familyName: "jansen" }; 26 | getFullName(p2); // Error 27 | 28 | } 29 | -------------------------------------------------------------------------------- /Chapter09/test/calculator.test.ts: -------------------------------------------------------------------------------- 1 | import { expect } from "chai"; 2 | import { Calculator } from "../src/calculator"; 3 | 4 | describe("Calculator", () => { 5 | 6 | it ("Should be able to calculate operations", () => { 7 | const calculator = new Calculator(); 8 | const addResult = calculator.calculate("add", 2, 3); 9 | expect(addResult).to.eql(5); 10 | const powResult = calculator.calculate("pow", 2, 3); 11 | expect(powResult).to.eql(8); 12 | }); 13 | 14 | it ("Should throw if an invalid operation is provided", () => { 15 | const calculator = new Calculator(); 16 | const throws = () => calculator.calculate("division", 2, 3); 17 | expect(throws).to.throw(); 18 | }); 19 | 20 | }); 21 | -------------------------------------------------------------------------------- /Chapter03/04_optional_parameters.ts: -------------------------------------------------------------------------------- 1 | namespace optional_parameters { 2 | 3 | function add1(foo: number, bar: number, foobar: number): number { 4 | return foo + bar + foobar; 5 | } 6 | 7 | add1(); // Error, expected 3 arguments, but got 0. 8 | add1(2, 2); // Error, expected 3 arguments, but got 2. 9 | add1(2, 2, 2); // OK, returns 6 10 | 11 | function add2(foo: number, bar: number, foobar?: number): number { 12 | let result = foo + bar; 13 | if (foobar !== undefined) { 14 | result += foobar; 15 | } 16 | return result; 17 | } 18 | 19 | add2(); // Error, expected 2-3 arguments, but got 0. 20 | add2(2, 2); // OK, returns 4 21 | add1(2, 2, 2); // OK, returns 6 22 | 23 | } 24 | -------------------------------------------------------------------------------- /Chapter03/09_function_scope.ts: -------------------------------------------------------------------------------- 1 | namespace scope { 2 | 3 | function foo1(): void { 4 | if (true) { 5 | var bar: number = 0; 6 | } 7 | console.log(bar); 8 | } 9 | 10 | foo1(); // 0 11 | 12 | function foo2(): void { 13 | bar = 0; 14 | var bar: number; 15 | console.log(bar); 16 | } 17 | 18 | foo2(); // 0 19 | 20 | function foo3(): void { 21 | if (true) { 22 | let bar: number = 0; 23 | bar = 1; 24 | } 25 | console.log(bar); // Error 26 | } 27 | 28 | function foo4(): void { 29 | if (true) { 30 | const bar: number = 0; 31 | bar = 1; // Error 32 | } 33 | console.log(bar); // Error 34 | } 35 | 36 | } 37 | -------------------------------------------------------------------------------- /Chapter07/08_pipes.ts: -------------------------------------------------------------------------------- 1 | namespace pipes_demo { 2 | 3 | function curry3(fn: (a: T1, b: T2, c: T3) => T4) { 4 | return (a: T1) => (b: T2) => (c: T3) => fn(a, b, c); 5 | } 6 | 7 | const pipe = (...fns: Array<(arg: T) => T>) => 8 | (value: T) => fns.reduce((acc, fn) => fn(acc), value); 9 | 10 | const trim = (s: string) => s.trim(); 11 | const capitalize = (s: string) => s.toUpperCase(); 12 | 13 | const replace = curry3( 14 | (s: string, f: string, r: string) => s.split(f).join(r) 15 | ); 16 | 17 | const trimCapitalizeAndReplace = pipe( 18 | trim, 19 | capitalize, 20 | replace("/")("-") 21 | ); 22 | 23 | trimCapitalizeAndReplace(" 13/feb/1989 "); // "13-FEB-1989" 24 | 25 | } 26 | -------------------------------------------------------------------------------- /Chapter08/04_parameter_decorator.ts: -------------------------------------------------------------------------------- 1 | import { readMetadata, addMetadata } from "./decorators"; 2 | 3 | namespace method_decorator_demo { 4 | 5 | class Person { 6 | 7 | public name: string; 8 | public surname: string; 9 | 10 | public constructor(name: string, surname: string) { 11 | this.name = name; 12 | this.surname = surname; 13 | } 14 | 15 | @readMetadata 16 | public saySomething(@addMetadata something: string): string { 17 | return `${this.name} ${this.surname} says: ${something}`; 18 | } 19 | 20 | } 21 | 22 | const person = new Person("Michael", "Jackson"); 23 | person.saySomething("Annie, are you ok?"); 24 | // saySomething arg[0]: "Annie, are you ok?" 25 | 26 | } 27 | -------------------------------------------------------------------------------- /Chapter03/15_covariant_arguments.ts: -------------------------------------------------------------------------------- 1 | namespace covariant_callbacks { 2 | 3 | declare function someFunc( 4 | callback: ( 5 | nestedCallback: (error: number, result: any) => void 6 | ) => void 7 | ): void; 8 | 9 | someFunc( 10 | ( 11 | nestedCallback: (e: number) => void // Error 12 | ) => { 13 | nestedCallback(1); 14 | } 15 | ); 16 | 17 | someFunc( 18 | ( 19 | nestedCallback: (e: number, result: any) => void // OK 20 | ) => { 21 | nestedCallback(1, 1); 22 | } 23 | ); 24 | 25 | } 26 | 27 | namespace covariant_promises { 28 | 29 | let p: Promise = new Promise((res, rej) => { 30 | res("error"); // Error 31 | }); 32 | 33 | } 34 | -------------------------------------------------------------------------------- /Chapter04/19_polymorphism.ts: -------------------------------------------------------------------------------- 1 | namespace polymorphism { 2 | 3 | abstract class Shape { 4 | public abstract area(): number; 5 | } 6 | 7 | class Rectangle extends Shape { 8 | public width!: number; 9 | public height!: number; 10 | public area() { 11 | return this.width * this.height; 12 | } 13 | } 14 | 15 | class Circle implements Shape { 16 | public radius!: number; 17 | public area() { 18 | return (this.radius * this.radius * Math.PI); 19 | } 20 | } 21 | 22 | class AreaCalculator { 23 | public area(shapes: Shape[]) { 24 | return shapes.reduce( 25 | (p, c) => p + c.area(), 26 | 0 27 | ); 28 | } 29 | } 30 | 31 | } 32 | -------------------------------------------------------------------------------- /Chapter08/02_method_decorator.ts: -------------------------------------------------------------------------------- 1 | import { logMethod } from "./decorators"; 2 | 3 | namespace method_decorator_demo { 4 | 5 | class Person { 6 | 7 | public name: string; 8 | public surname: string; 9 | 10 | public constructor(name: string, surname: string) { 11 | this.name = name; 12 | this.surname = surname; 13 | } 14 | 15 | @logMethod 16 | public saySomething(something: string): string { 17 | return `${this.name} ${this.surname} says: ${something}`; 18 | } 19 | 20 | } 21 | 22 | const person = new Person("Michael", "Jackson"); 23 | person.saySomething("Annie, are you ok?"); 24 | // Call: saySomething("Annie, are you ok?") => "Michael Jackson says: Annie, are you ok?" 25 | 26 | } 27 | -------------------------------------------------------------------------------- /Chapter10/10_inversify_express_utils/index.ts: -------------------------------------------------------------------------------- 1 | import "reflect-metadata"; 2 | import { Container } from "inversify"; 3 | import * as bodyParser from "body-parser"; 4 | import { InversifyExpressServer } from "inversify-express-utils"; 5 | import { bindings } from "./inversify.config"; 6 | 7 | (async () => { 8 | 9 | const port = 3000; 10 | const container = new Container(); 11 | await container.loadAsync(bindings); 12 | const app = new InversifyExpressServer(container); 13 | app.setConfig((a) => { 14 | a.use(bodyParser.json()); 15 | a.use(bodyParser.urlencoded({ extended: true })); 16 | }); 17 | const server = app.build(); 18 | 19 | server.listen(port, () => { 20 | console.log(`Server running at http://127.0.0.1:${port}/`) 21 | }); 22 | 23 | })(); 24 | -------------------------------------------------------------------------------- /Chapter10/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "app1", 3 | "version": "1.0.0", 4 | "description": "", 5 | "main": "index.js", 6 | "scripts": { 7 | "test": "echo \"Error: no test specified\" && exit 1" 8 | }, 9 | "keywords": [], 10 | "author": "Remo H. Jansen (http://www.remojansen.com)", 11 | "license": "ISC", 12 | "dependencies": { 13 | "express": "4.16.2", 14 | "glob": "7.1.2", 15 | "inversify": "4.11.1", 16 | "inversify-express-utils": "5.2.1", 17 | "pg": "7.4.1", 18 | "reflect-metadata": "0.1.12", 19 | "typeorm": "0.1.14", 20 | "yargs": "11.0.0" 21 | }, 22 | "devDependencies": { 23 | "@types/express": "4.11.1", 24 | "@types/node": "9.4.6", 25 | "@types/glob": "5.0.35", 26 | "@types/yargs": "11.0.0" 27 | } 28 | } 29 | -------------------------------------------------------------------------------- /Chapter12/web/frontend/app-routing.module.ts: -------------------------------------------------------------------------------- 1 | import { NgModule } from "@angular/core"; 2 | import { Routes, RouterModule } from "@angular/router"; 3 | import { HomePageComponent } from "./pages/homepage.component"; 4 | import { MoviesPageComponent } from "./pages/moviespage.component"; 5 | import { ActorsPageComponent } from "./pages/actorspage.component"; 6 | 7 | export const appRoutes: Routes = [ 8 | { path: "", component: HomePageComponent }, 9 | { path: "movies", component: MoviesPageComponent }, 10 | { path: "actors", component: ActorsPageComponent } 11 | ]; 12 | 13 | @NgModule({ 14 | exports: [RouterModule], 15 | imports: [ 16 | RouterModule.forRoot( 17 | appRoutes, 18 | { useHash: false } 19 | ) 20 | ] 21 | }) 22 | 23 | export class AppRoutingModule {} 24 | -------------------------------------------------------------------------------- /Chapter02/27_mapped_type_modifiers.ts: -------------------------------------------------------------------------------- 1 | namespace mapped_type_modifiers_demo { 2 | 3 | // Creates a type with all the properties in T, 4 | // but marked both readonly and optional. 5 | type ReadonlyAndPartial1 = { 6 | readonly [P in keyof T]?: T[P] 7 | } 8 | 9 | type ReadonlyAndPartial2 = { 10 | +readonly [P in keyof T]+?: T[P]; 11 | } 12 | 13 | type Mutable = { 14 | -readonly [P in keyof T]: T[P] 15 | } 16 | 17 | interface Foo { 18 | readonly abc: number; 19 | def?: string; 20 | } 21 | 22 | // 'abc' is no longer read-only, but 'def' is still optional. 23 | type TotallyMutableFoo = Mutable 24 | 25 | // Make all properties in T required 26 | type Required = { 27 | [P in keyof T]-?: T[P]; 28 | } 29 | 30 | } -------------------------------------------------------------------------------- /Chapter01/10_modules.ts: -------------------------------------------------------------------------------- 1 | namespace module_demo { 2 | 3 | namespace geometry { 4 | 5 | interface VectorInterface { 6 | /* ... */ 7 | } 8 | 9 | export interface Vector2dInterface { 10 | /* ... */ 11 | } 12 | 13 | export interface Vector3dInterface { 14 | /* ... */ 15 | } 16 | 17 | export class Vector2d 18 | implements VectorInterface, Vector2dInterface { 19 | /* ... */ 20 | } 21 | 22 | export class Vector3d implements VectorInterface, Vector3dInterface { 23 | /* ... */ 24 | } 25 | 26 | } 27 | 28 | let vector2dInstance: geometry.Vector2dInterface = new geometry.Vector2d(); 29 | let vector3dInstance: geometry.Vector3dInterface = new geometry.Vector3d(); 30 | 31 | } 32 | -------------------------------------------------------------------------------- /Chapter03/11_tag_templates.ts: -------------------------------------------------------------------------------- 1 | namespace tag_template { 2 | 3 | let name = "remo"; 4 | let surname = "jansen"; 5 | let html1 = `

${name} ${surname}

`; 6 | 7 | function htmlEscape(literals: TemplateStringsArray, ...placeholders: any[]) { 8 | let result = ""; 9 | for (let i = 0; i < placeholders.length; i++) { 10 | result += literals[i]; 11 | result += placeholders[i] 12 | .replace(/&/g, "&") 13 | .replace(/"/g, """) 14 | .replace(/"/g, "'") 15 | .replace(//g, ">"); 17 | } 18 | result += literals[literals.length - 1]; 19 | return result; 20 | } 21 | 22 | let html2 = htmlEscape `

${name} ${surname}

`; 23 | 24 | } 25 | -------------------------------------------------------------------------------- /Chapter03/19_async_iterators.ts: -------------------------------------------------------------------------------- 1 | // Remember to enable 2 | // "lib": [ "es2015.promise", "dom", "es5", "es2015.generator", "es2015.iterable" ] 3 | // "downlevelIteration": true 4 | // in tsconfig.json 5 | namespace async_iterator_demo { 6 | 7 | let counter = 0; 8 | 9 | function doSomethingAsync() { 10 | return new Promise((r) => { 11 | setTimeout(() => { 12 | counter += 1; 13 | r(counter); 14 | }, 1000); 15 | }); 16 | } 17 | 18 | async function* g1() { 19 | yield await doSomethingAsync(); 20 | yield await doSomethingAsync(); 21 | yield await doSomethingAsync(); 22 | } 23 | 24 | async function func() { 25 | for await (const x of g1()) { 26 | console.log(x); 27 | } 28 | } 29 | 30 | } 31 | -------------------------------------------------------------------------------- /Chapter14/02_testing/test/pow.e2e.ts: -------------------------------------------------------------------------------- 1 | import { NightwatchBrowser } from "nightwatch"; 2 | 3 | // This is an end-to-end test. We use a real web browser 4 | // to simulate a real user and check that everything 5 | // works as expected. 6 | 7 | const test = { 8 | "Calculator pow e2e test example": (browser: NightwatchBrowser) => { 9 | browser 10 | .url("http://localhost:3000/") 11 | .waitForElementVisible("body", 1000) 12 | .assert.title("Calculator") 13 | .assert.visible("#base") 14 | .assert.visible("#exponent") 15 | .clearValue("#base") 16 | .setValue("#base", "2") 17 | .clearValue("#exponent") 18 | .setValue("#exponent", "3") 19 | .click("#submit_btn") 20 | .pause(500) 21 | .assert.containsText("#result", "8") 22 | .end(); 23 | } 24 | }; 25 | 26 | export = test; 27 | -------------------------------------------------------------------------------- /Chapter09/src/calculator.ts: -------------------------------------------------------------------------------- 1 | import { add } from "./operations/add"; 2 | import { pow } from "./operations/pow"; 3 | 4 | interface Operation { 5 | name: string; 6 | operation(a: number, b: number): number; 7 | } 8 | 9 | export class Calculator { 10 | private readonly _operations: Operation[]; 11 | public constructor() { 12 | this._operations = [ 13 | { name: "add", operation: add }, 14 | { name: "pow", operation: pow } 15 | ]; 16 | } 17 | public calculate(operation: string, a: number, b: number) { 18 | const opt = this._operations.filter((o) => o.name === operation)[0]; 19 | if (opt === undefined) { 20 | throw new Error(`The operation ${operation} is not available!`); 21 | } else { 22 | return opt.operation(a, b); 23 | } 24 | } 25 | } 26 | -------------------------------------------------------------------------------- /Chapter10/08_express_middleware.ts: -------------------------------------------------------------------------------- 1 | import express from "express"; 2 | 3 | const port = 3000; 4 | const app = express(); 5 | 6 | const timerMiddleware = ( 7 | req: express.Request, 8 | res: express.Response, 9 | next: express.NextFunction 10 | ) => { 11 | console.log(`Time: ${Date.now()}`); 12 | next(); 13 | }; 14 | 15 | const loggerMiddleware = ( 16 | req: express.Request, 17 | res: express.Response, 18 | next: express.NextFunction 19 | ) => { 20 | console.log(`URL: ${req.url}`); 21 | next(); 22 | }; 23 | 24 | // Application level middlware 25 | app.use(timerMiddleware); 26 | 27 | // Route level middleware 28 | app.get("/", loggerMiddleware, (req, res) => { 29 | res.send("Hello World!"); 30 | }); 31 | 32 | app.listen(port, () => { 33 | console.log(`Server running at http://127.0.0.1:${port}/`); 34 | }); 35 | -------------------------------------------------------------------------------- /Chapter14/02_testing/test/pow_service.test.ts: -------------------------------------------------------------------------------- 1 | import { expect } from "chai"; 2 | import * as request from "supertest"; 3 | import { getApp } from "../src/backend/server"; 4 | 5 | // This is an integration test of a backend service 6 | // powered by node.js and express.js 7 | // We send an HTTP GET request and check that the 8 | // response works as expected. 9 | 10 | describe("Math Service", function() { 11 | it("HTTP GET /api/math/pow/:base/:exponent", async () => { 12 | const app = getApp(); 13 | return request(app).get("/api/math/pow/2/3") 14 | .set("Accept", "application/json") 15 | .expect("Content-Type", /json/) 16 | .expect(200) 17 | .then((response) => 18 | expect(response.body.result).eql(8) 19 | ); 20 | }); 21 | }); 22 | -------------------------------------------------------------------------------- /Chapter08/03_property_decorator.ts: -------------------------------------------------------------------------------- 1 | import { logProperty } from "./decorators"; 2 | 3 | namespace method_decorator_demo { 4 | 5 | class Person { 6 | 7 | @logProperty 8 | public name: string; 9 | 10 | @logProperty 11 | public surname: string; 12 | 13 | public constructor(name: string, surname: string) { 14 | this.name = name; 15 | this.surname = surname; 16 | } 17 | 18 | public saySomething(something: string): string { 19 | return `${this.name} ${this.surname} says: ${something}`; 20 | } 21 | 22 | } 23 | 24 | const person = new Person("Michael", "Jackson"); 25 | // Set: name => Michael 26 | // Set: surname => Jackson 27 | 28 | person.saySomething("Annie, are you ok?"); 29 | // Get: name => Michael 30 | // Get: surname => Jackson 31 | 32 | } 33 | -------------------------------------------------------------------------------- /Chapter03/18_async_generators.ts: -------------------------------------------------------------------------------- 1 | // Remember to enable 2 | // "lib": [ "es2015.promise", "dom", "es5", "es2015.generator", "es2015.iterable" ] 3 | // "downlevelIteration": true 4 | // in tsconfig.json 5 | namespace async_iterator_demo { 6 | 7 | let counter = 0; 8 | 9 | function doSomethingAsync() { 10 | return new Promise((r) => { 11 | setTimeout(() => { 12 | counter += 1; 13 | r(counter); 14 | }, 1000); 15 | }); 16 | } 17 | 18 | async function* g1() { 19 | yield await doSomethingAsync(); 20 | yield await doSomethingAsync(); 21 | yield await doSomethingAsync(); 22 | } 23 | 24 | let i = g1(); 25 | i.next().then((n) => console.log(n)); // 1 26 | i.next().then((n) => console.log(n)); // 2 27 | i.next().then((n) => console.log(n)); // 3 28 | 29 | } 30 | -------------------------------------------------------------------------------- /Chapter07/01_pure_functions.ts: -------------------------------------------------------------------------------- 1 | namespace impure_function { 2 | 3 | function isIndexPage() { 4 | return window.location.pathname === "/"; 5 | } 6 | 7 | } 8 | 9 | namespace pure_function { 10 | 11 | function isIndexPage(pathname: string) { 12 | return pathname === "/"; 13 | } 14 | 15 | function shouldReturnTrueWhenPathIsIndex(){ 16 | let expected = true; 17 | let result = isIndexPage("/"); 18 | if (expected !== result) { 19 | throw new Error(`Expected ${expected} to equals ${result}`); 20 | } 21 | } 22 | 23 | function shouldReturnFalseWhenPathIsNotIndex() { 24 | let expected = false; 25 | let result = isIndexPage("/someotherpage"); 26 | if (expected !== result) { 27 | throw new Error(`Expected ${expected} to equals ${result}`); 28 | } 29 | } 30 | 31 | } 32 | -------------------------------------------------------------------------------- /Chapter10/09_mvc/db.ts: -------------------------------------------------------------------------------- 1 | import { createConnection } from "typeorm"; 2 | 3 | import { Movie } from "./entities/movie"; 4 | 5 | export async function getDbConnection() { 6 | 7 | const DATABASE_HOST = process.env.DATABASE_HOST || "localhost"; 8 | const DATABASE_USER = process.env.DATABASE_USER || ""; 9 | const DATABASE_PORT = 5432; 10 | const DATABASE_PASSWORD = process.env.DATABASE_PASSWORD || ""; 11 | const DATABASE_DB = "demo"; 12 | 13 | const entities = [ 14 | Movie 15 | ]; 16 | 17 | const conn = await createConnection({ 18 | type: "postgres", 19 | host: DATABASE_HOST, 20 | port: DATABASE_PORT, 21 | username: DATABASE_USER, 22 | password: DATABASE_PASSWORD, 23 | database: DATABASE_DB, 24 | entities: entities, 25 | synchronize: true 26 | }); 27 | 28 | return conn; 29 | 30 | } 31 | -------------------------------------------------------------------------------- /Chapter02/14_discriminated_unions.ts: -------------------------------------------------------------------------------- 1 | namespace discriminated_unions_demo { 2 | 3 | interface Cube { 4 | kind: "cube"; 5 | size: number; 6 | } 7 | 8 | interface Pyramid { 9 | kind: "pyramid"; 10 | width: number; 11 | length: number; 12 | height: number; 13 | } 14 | 15 | interface Sphere { 16 | kind: "sphere"; 17 | radius: number; 18 | } 19 | 20 | type Shape = Cube | Pyramid | Sphere; 21 | 22 | function volume(shape: Shape) { 23 | const PI = Math.PI; 24 | switch (shape.kind) { 25 | case "cube": 26 | return shape.size ** 3; 27 | case "pyramid": 28 | return (shape.width * shape.height * shape.length) / 3; 29 | case "sphere": 30 | return (4 / 3) * PI * (shape.radius ** 3); 31 | } 32 | } 33 | 34 | } 35 | -------------------------------------------------------------------------------- /Chapter11/web/frontend/components/counter.tsx: -------------------------------------------------------------------------------- 1 | import * as React from "react"; 2 | import { Button } from "./button_component"; 3 | 4 | interface CounterProps { 5 | initialValue: number; 6 | } 7 | 8 | interface CounterState { 9 | value: number; 10 | } 11 | 12 | export class Component extends React.Component { 13 | public constructor(props: CounterProps) { 14 | super(props); 15 | this.state = { value: this.props.initialValue }; 16 | } 17 | public render() { 18 | return ( 19 |
20 | The value is: {this.state.value} 21 | 24 |
25 | ); 26 | } 27 | private _increment() { 28 | this.setState({ value: this.state.value + 1 }); 29 | } 30 | } 31 | -------------------------------------------------------------------------------- /Chapter10/10_inversify_express_utils/db.ts: -------------------------------------------------------------------------------- 1 | import { createConnection } from "typeorm"; 2 | 3 | import { Movie } from "./entities/movie"; 4 | 5 | export async function getDbConnection() { 6 | 7 | const DATABASE_HOST = process.env.DATABASE_HOST || "localhost"; 8 | const DATABASE_USER = process.env.DATABASE_USER || ""; 9 | const DATABASE_PORT = 5432; 10 | const DATABASE_PASSWORD = process.env.DATABASE_PASSWORD || ""; 11 | const DATABASE_DB = "demo"; 12 | 13 | const entities = [ 14 | Movie 15 | ]; 16 | 17 | const conn = await createConnection({ 18 | type: "postgres", 19 | host: DATABASE_HOST, 20 | port: DATABASE_PORT, 21 | username: DATABASE_USER, 22 | password: DATABASE_PASSWORD, 23 | database: DATABASE_DB, 24 | entities: entities, 25 | synchronize: true 26 | }); 27 | 28 | return conn; 29 | 30 | } 31 | -------------------------------------------------------------------------------- /Chapter12/web/frontend/config/layout.module.ts: -------------------------------------------------------------------------------- 1 | import { NgModule } from "@angular/core"; 2 | import { RouterModule } from "@angular/router"; 3 | import { CommonModule } from "@angular/common"; 4 | import { LayoutComponent } from "./layout.component"; 5 | import { MoviesPageModule } from "../pages/moviespage.module"; 6 | import { ActorsPageModule } from "../pages/actorspage.module"; 7 | import { HomePageModule } from "../pages/homepage.module"; 8 | import { ComponentsModule } from "../components/components.module"; 9 | 10 | @NgModule({ 11 | declarations: [ 12 | LayoutComponent 13 | ], 14 | exports: [ 15 | LayoutComponent 16 | ], 17 | imports: [ 18 | RouterModule, 19 | CommonModule, 20 | MoviesPageModule, 21 | ActorsPageModule, 22 | HomePageModule, 23 | ComponentsModule 24 | ] 25 | }) 26 | export class LayoutModule { 27 | } 28 | -------------------------------------------------------------------------------- /Chapter11/web/backend/db.ts: -------------------------------------------------------------------------------- 1 | import { createConnection } from "typeorm"; 2 | import { Actor } from "./entities/actor"; 3 | import { Movie } from "./entities/movie"; 4 | 5 | export async function getDbConnection() { 6 | 7 | const DATABASE_HOST = process.env.DATABASE_HOST || "localhost"; 8 | const DATABASE_USER = process.env.DATABASE_USER || ""; 9 | const DATABASE_PORT = 5432; 10 | const DATABASE_PASSWORD = process.env.DATABASE_PASSWORD || ""; 11 | const DATABASE_DB = "demo"; 12 | 13 | const entities = [ 14 | Actor, 15 | Movie 16 | ]; 17 | 18 | const conn = await createConnection({ 19 | database: DATABASE_DB, 20 | entities, 21 | host: DATABASE_HOST, 22 | password: DATABASE_PASSWORD, 23 | port: DATABASE_PORT, 24 | synchronize: true, 25 | type: "postgres", 26 | username: DATABASE_USER 27 | }); 28 | 29 | return conn; 30 | 31 | } 32 | -------------------------------------------------------------------------------- /Chapter12/web/backend/db.ts: -------------------------------------------------------------------------------- 1 | import { createConnection } from "typeorm"; 2 | import { Actor } from "./entities/actor"; 3 | import { Movie } from "./entities/movie"; 4 | 5 | export async function getDbConnection() { 6 | 7 | const DATABASE_HOST = process.env.DATABASE_HOST || "localhost"; 8 | const DATABASE_USER = process.env.DATABASE_USER || ""; 9 | const DATABASE_PORT = 5432; 10 | const DATABASE_PASSWORD = process.env.DATABASE_PASSWORD || ""; 11 | const DATABASE_DB = "demo"; 12 | 13 | const entities = [ 14 | Actor, 15 | Movie 16 | ]; 17 | 18 | const conn = await createConnection({ 19 | database: DATABASE_DB, 20 | entities, 21 | host: DATABASE_HOST, 22 | password: DATABASE_PASSWORD, 23 | port: DATABASE_PORT, 24 | synchronize: true, 25 | type: "postgres", 26 | username: DATABASE_USER 27 | }); 28 | 29 | return conn; 30 | 31 | } 32 | -------------------------------------------------------------------------------- /tslint.json: -------------------------------------------------------------------------------- 1 | { 2 | "defaultSeverity": "error", 3 | "extends": [ 4 | "tslint:recommended" 5 | ], 6 | "jsRules": {}, 7 | "rules": { 8 | "no-namespace": false, 9 | "prefer-const": false, 10 | "no-var-keyword": false, 11 | "no-var-requires": false, 12 | "only-arrow-functions": [ 13 | false 14 | ], 15 | "trailing-comma": [ 16 | false 17 | ], 18 | "no-empty-interface": false, 19 | "interface-name": [false], 20 | "max-classes-per-file": [false], 21 | "variable-name": false, 22 | "no-console": [false], 23 | "object-literal-sort-keys": false, 24 | "no-angle-bracket-type-assertion": false, 25 | "no-string-literal": false, 26 | "unified-signatures": false, 27 | "arrow-parens": false, 28 | "curly": false, 29 | "ban-types": false 30 | }, 31 | "rulesDirectory": [] 32 | } 33 | -------------------------------------------------------------------------------- /Chapter11/web/backend/inversify.config.ts: -------------------------------------------------------------------------------- 1 | import { AsyncContainerModule } from "inversify"; 2 | import { Repository } from "typeorm"; 3 | import { TYPE } from "./constants/types"; 4 | import { getDbConnection } from "./db"; 5 | import { Actor } from "./entities/actor"; 6 | import { Movie } from "./entities/movie"; 7 | import { getActorRepository } from "./repositories/actor_repository"; 8 | import { getMovieRepository } from "./repositories/movie_repository"; 9 | 10 | export const bindings = new AsyncContainerModule(async (bind) => { 11 | 12 | await getDbConnection(); 13 | await require("./controllers/movie_controller"); 14 | await require("./controllers/actor_controller"); 15 | 16 | bind>(TYPE.MovieRepository) 17 | .toDynamicValue(getMovieRepository) 18 | .inRequestScope(); 19 | 20 | bind>(TYPE.ActorRepository) 21 | .toDynamicValue(getActorRepository) 22 | .inRequestScope(); 23 | 24 | }); 25 | -------------------------------------------------------------------------------- /Chapter12/web/backend/inversify.config.ts: -------------------------------------------------------------------------------- 1 | import { AsyncContainerModule } from "inversify"; 2 | import { Repository } from "typeorm"; 3 | import { TYPE } from "./constants/types"; 4 | import { getDbConnection } from "./db"; 5 | import { Actor } from "./entities/actor"; 6 | import { Movie } from "./entities/movie"; 7 | import { getActorRepository } from "./repositories/actor_repository"; 8 | import { getMovieRepository } from "./repositories/movie_repository"; 9 | 10 | export const bindings = new AsyncContainerModule(async (bind) => { 11 | 12 | await getDbConnection(); 13 | await require("./controllers/movie_controller"); 14 | await require("./controllers/actor_controller"); 15 | 16 | bind>(TYPE.MovieRepository) 17 | .toDynamicValue(getMovieRepository) 18 | .inRequestScope(); 19 | 20 | bind>(TYPE.ActorRepository) 21 | .toDynamicValue(getActorRepository) 22 | .inRequestScope(); 23 | 24 | }); 25 | -------------------------------------------------------------------------------- /Chapter06/04_prototypes.ts: -------------------------------------------------------------------------------- 1 | namespace prototypes { 2 | 3 | class Person { 4 | 5 | public name: string; 6 | public surname: string; 7 | public age: number = 0; 8 | 9 | public constructor(name: string, surname: string) { 10 | this.name = name; 11 | this.surname = surname; 12 | } 13 | 14 | public greet() { 15 | var msg = `Hi! my name is ${this.name} ${this.surname}`; 16 | msg += `I'm ${this.age}`; 17 | } 18 | 19 | } 20 | 21 | class SuperHero extends Person { 22 | public superpower: string; 23 | public constructor( 24 | name: string, 25 | surname: string, 26 | superpower: string 27 | ) { 28 | super(name, surname); 29 | this.superpower = superpower; 30 | } 31 | public userSuperPower() { 32 | return `I'm using my ${this.superpower}`; 33 | } 34 | } 35 | 36 | } 37 | -------------------------------------------------------------------------------- /Chapter14/02_testing/test/match_client.test.ts: -------------------------------------------------------------------------------- 1 | import { expect } from "chai"; 2 | import { stub } from "sinon"; 3 | import { MathClient } from "../src/frontend/math_client"; 4 | 5 | // Here we will write some unit test for the math client 6 | // it demostrates how we can use dependency injection to 7 | // replace dependencies with fake dependencies 8 | 9 | describe("MathDemo", () => { 10 | it("Should return result of pow calculation", async () => { 11 | 12 | const expectedResult = "8"; 13 | 14 | const response = { 15 | json: () => Promise.resolve({ result: expectedResult }) 16 | }; 17 | 18 | const stubedFetch = stub(global, "fetch" as any); 19 | stubedFetch.returns(Promise.resolve(response)); 20 | 21 | const mathClient = new MathClient(); 22 | const actualResult = await mathClient.pow(2, 3); 23 | expect(expectedResult).to.eq(actualResult); 24 | expect(stubedFetch.callCount).to.eq(1); 25 | 26 | }); 27 | }); 28 | -------------------------------------------------------------------------------- /Chapter11/web/frontend/pages/home_page.tsx: -------------------------------------------------------------------------------- 1 | import * as React from "react"; 2 | import { Card } from "../components/card_component"; 3 | import { Container, Row, Column } from "../components/grid_component"; 4 | 5 | export const HomePage = () => ( 6 | 7 | 8 | 9 | 16 | 17 | 18 | 25 | 26 | 27 | 28 | ); 29 | -------------------------------------------------------------------------------- /Chapter14/02_testing/nightwatch.json: -------------------------------------------------------------------------------- 1 | { 2 | "src_folders": [ 3 | "./dist/test" 4 | ], 5 | "output_folder": "reports", 6 | "custom_commands_path": "", 7 | "custom_assertions_path": "", 8 | "page_objects_path": "", 9 | "globals_path": "./globals.js", 10 | "selenium": { 11 | "start_process": false 12 | }, 13 | "test_settings": { 14 | "default": { 15 | "selenium_port": 9515, 16 | "selenium_host": "localhost", 17 | "default_path_prefix": "", 18 | "desiredCapabilities": { 19 | "browserName": "chrome", 20 | "chromeOptions": { 21 | "args": [ 22 | "--no-sandbox" 23 | ] 24 | }, 25 | "acceptSslCerts": true 26 | } 27 | }, 28 | "chrome": { 29 | "desiredCapabilities": { 30 | "browserName": "chrome" 31 | } 32 | } 33 | } 34 | } -------------------------------------------------------------------------------- /Chapter05/10_ioc_containers.ts: -------------------------------------------------------------------------------- 1 | import { Container, inject, injectable } from "inversify"; 2 | import "reflect-metadata"; 3 | 4 | namespace ioc_demo { 5 | 6 | interface Weapon { 7 | tryHit(fromDistance: number): boolean; 8 | } 9 | 10 | @injectable() 11 | class Katana implements Weapon { 12 | public tryHit(fromDistance: number) { 13 | return fromDistance <= 2; 14 | } 15 | } 16 | 17 | @injectable() 18 | class Ninja { 19 | public constructor( 20 | @inject("Weapon") private _weapon: Weapon 21 | ) {} 22 | public fight(fromDistance: number) { 23 | return this._weapon.tryHit(fromDistance); 24 | } 25 | } 26 | 27 | const container = new Container(); 28 | container.bind("Weapon").to(Katana); 29 | container.bind("Ninja").to(Ninja); 30 | 31 | const ninja = container.get("Ninja"); 32 | console.log(ninja.fight(2)); // true 33 | console.log(ninja.fight(5)); // false 34 | 35 | } 36 | -------------------------------------------------------------------------------- /Chapter12/web/frontend/components/listgroup.component.ts: -------------------------------------------------------------------------------- 1 | import { Component, Input } from "@angular/core"; 2 | 3 | @Component({ 4 | host : { 5 | "[class]": "itemClass" 6 | }, 7 | selector: "app-list-group-item", 8 | template: ` 9 | 10 | ` 11 | }) 12 | export class ListGroupItemComponent { 13 | public itemClass = "list-group-item"; 14 | } 15 | 16 | @Component({ 17 | selector: "app-list-group", 18 | template: ` 19 |
20 | 21 |
22 |
23 | 24 |
25 |
26 |
    27 | 28 |
29 |
30 | ` 31 | }) 32 | export class ListGroupComponent { 33 | @Input() public errorMsg!: string | null; 34 | @Input() public isLoaded!: boolean | null; 35 | } 36 | -------------------------------------------------------------------------------- /Chapter12/web/frontend/pages/homepage.component.ts: -------------------------------------------------------------------------------- 1 | import { Component } from "@angular/core"; 2 | 3 | @Component({ 4 | selector: "home-page", 5 | template: ` 6 | 7 | 8 | 9 | 15 | 16 | 17 | 23 | 24 | 25 | 26 | ` 27 | }) 28 | export class HomePageComponent { 29 | } 30 | -------------------------------------------------------------------------------- /Chapter11/web/frontend/components/card_component.tsx: -------------------------------------------------------------------------------- 1 | import { Link } from "react-router-dom"; 2 | import * as React from "react"; 3 | 4 | interface CardIMageProps { 5 | imgPath: string; 6 | imgAlt: string; 7 | } 8 | 9 | interface CardProps { 10 | title: string; 11 | description: string; 12 | linkPath: string; 13 | linkText: string; 14 | img: CardIMageProps | null; 15 | } 16 | 17 | export const CardIMage = (props: CardIMageProps) => ( 18 | {props.imgAlt} 19 | ); 20 | 21 | export const Card = (props: CardProps) => ( 22 |
23 | {props.img ? : null} 24 |
25 |
{props.title}
26 |

27 | {props.description} 28 |

29 | 30 | {props.linkText} 31 | 32 |
33 |
34 | ); 35 | -------------------------------------------------------------------------------- /Chapter01/07_functions.ts: -------------------------------------------------------------------------------- 1 | namespace functions_demo { 2 | 3 | // named function 4 | function greet1(name?: string): string { 5 | if (name) { 6 | return "Hi! " + name; 7 | } else { 8 | return "Hi!"; 9 | } 10 | } 11 | 12 | // anonymous function 13 | let greet2 = function(name?: string): string { 14 | if (name) { 15 | return "Hi! " + name; 16 | } else { 17 | return "Hi!"; 18 | } 19 | }; 20 | 21 | let greet3 = (name: string): string => { 22 | if (name) { 23 | return "Hi! " + name; 24 | } else { 25 | return "Hi!"; 26 | } 27 | }; 28 | 29 | let greet4: (name: string) => string = function(name: string): string { 30 | if (name) { 31 | return "Hi! " + name; 32 | } else { 33 | return "Hi!"; 34 | } 35 | }; 36 | 37 | function add( 38 | a: number, b: number, callback: (result: number) => void 39 | ) { 40 | callback(a + b); 41 | } 42 | 43 | } 44 | -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "learning-typescript", 3 | "version": "1.0.0", 4 | "description": "Source code of Learning TypeScript published by Packt Publishing", 5 | "main": "index.js", 6 | "scripts": { 7 | "test": "ts-node ./test/index.ts" 8 | }, 9 | "repository": { 10 | "type": "git", 11 | "url": "git+https://github.com/remojansen/LearningTypeScript.git" 12 | }, 13 | "keywords": [], 14 | "author": "Remo H. Jansen (http://www.remojansen.com)", 15 | "license": "MIT", 16 | "bugs": { 17 | "url": "https://github.com/remojansen/LearningTypeScript/issues" 18 | }, 19 | "homepage": "https://github.com/remojansen/LearningTypeScript#readme", 20 | "devDependencies": { 21 | "@types/ramda": "0.25.21", 22 | "@types/requirejs": "2.1.31", 23 | "chalk": "2.3.2", 24 | "ts-node": "5.0.1", 25 | "ts-simple-ast": "10.2.0", 26 | "typescript": "2.8.1" 27 | }, 28 | "dependencies": { 29 | "immutable": "3.8.2", 30 | "inversify": "4.11.1", 31 | "ramda": "0.25.0", 32 | "reflect-metadata": "0.1.12" 33 | } 34 | } 35 | -------------------------------------------------------------------------------- /Chapter07/13_immutable.ts: -------------------------------------------------------------------------------- 1 | import * as immutable from "immutable"; 2 | 3 | // npm install --save immutable 4 | 5 | namespace immutable_demo_1 { 6 | 7 | const map1 = immutable.Map({ a: 1, b: 2, c: 3 }); 8 | const map2 = map1.set("b", 50); 9 | console.log(`${map1.get("b")} vs.${map2.get("b")}`); 10 | // 2 vs. 50 11 | 12 | const nested = immutable.fromJS({ a: { b: { c: [ 3, 4, 5 ] } } }); 13 | 14 | const nested2 = nested.mergeDeep({ a: { b: { d: 6 } } }); 15 | // Map { a: Map { b: Map { c: List [ 3, 4, 5 ], d: 6 } } } 16 | 17 | console.log(nested2.getIn([ "a", "b", "d" ])); 18 | // 6 19 | 20 | const nested3 = nested2.updateIn( 21 | [ "a", "b", "d" ], 22 | (value: string) => value + 1 23 | ); 24 | 25 | console.log(nested3); 26 | // Map { a: Map { b: Map { c: List [ 3, 4, 5 ], d: 7 } } } 27 | 28 | const nested4 = nested3.updateIn( 29 | [ "a", "b", "c" ], 30 | (list: number[]) => list.push(6) 31 | ); 32 | 33 | console.log(nested4); 34 | // Map { a: Map { b: Map { c: List [ 3, 4, 5, 6 ], d: 7 } } } 35 | 36 | } 37 | -------------------------------------------------------------------------------- /Chapter04/07_optional_properties.ts: -------------------------------------------------------------------------------- 1 | namespace optional_properties { 2 | 3 | class Vector { 4 | 5 | public constructor( 6 | public x: number, 7 | public y: number, 8 | public z?: number 9 | ) {} 10 | 11 | public is3D() { 12 | return this.z !== undefined; 13 | } 14 | 15 | public is2D() { 16 | return this.z === undefined; 17 | } 18 | 19 | } 20 | 21 | const vector2D = new Vector(0, 0); 22 | vector2D.is2D(); // true 23 | vector2D.is3D(); // false 24 | 25 | const lenght1 = Math.sqrt( 26 | vector2D.x * vector2D.x + 27 | vector2D.y * vector2D.y + 28 | vector2D.z * vector2D.z // Error: Object is possibly 'undefined' 29 | ); 30 | 31 | const vector3D = new Vector(0, 0 , 0); 32 | vector3D.is2D(); // false 33 | vector3D.is3D(); // true 34 | 35 | const lenght2 = Math.sqrt( 36 | vector3D.x * vector3D.x + 37 | vector3D.y * vector3D.y + 38 | ((vector3D.z !== undefined) ? (vector3D.z * vector3D.z) : 0) // OK 39 | ); 40 | 41 | } 42 | -------------------------------------------------------------------------------- /Chapter02/15_never_type.ts: -------------------------------------------------------------------------------- 1 | namespace never_type_demo { 2 | 3 | function error(message: string): never { 4 | throw new Error(message); 5 | } 6 | 7 | // Type () => never 8 | const sing = function() { 9 | while (true) { 10 | console.log("I will never return!"); 11 | } 12 | }; 13 | 14 | interface Square { 15 | kind: "square"; 16 | size: number; 17 | } 18 | 19 | interface Rectangle { 20 | kind: "rectangle"; 21 | width: number; 22 | height: number; 23 | } 24 | 25 | interface Circle { 26 | kind: "circle"; 27 | radius: number; 28 | } 29 | 30 | type Shape = Square | Rectangle | Circle; 31 | 32 | function area(shape: Shape) { 33 | const PI = Math.PI; 34 | switch (shape.kind) { 35 | case "square": return shape.size * shape.size; 36 | case "rectangle": return shape.width * shape.height; 37 | case "circle": return PI * shape.radius * shape.radius; 38 | default: 39 | return shape; // never 40 | } 41 | } 42 | 43 | } 44 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2019 Packt 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /Chapter06/03_bind.ts: -------------------------------------------------------------------------------- 1 | namespace bind_demo { 2 | 3 | class Person { 4 | 5 | public name: string; 6 | public surname: string; 7 | 8 | public constructor(name: string, surname: string) { 9 | this.name = name; 10 | this.surname = surname; 11 | } 12 | 13 | public greet(city: string, country: string) { 14 | // we use the “this” operator to access name and surname 15 | let msg = `Hi, my name is ${this.name} ${this.surname}.`; 16 | msg += `I'm from ${city} (${country}).`; 17 | console.log(msg); 18 | } 19 | 20 | } 21 | 22 | const person = new Person("Remo", "Jansen"); 23 | const greet = person.greet.bind(person); 24 | 25 | greet.call(person, "Seville", "Spain"); 26 | greet.apply(person, ["Seville", "Spain"]); 27 | 28 | greet.call(null, "Seville", "Spain"); 29 | greet.apply(null, ["Seville", "Spain"]); 30 | 31 | const valueOfThis = { name: "anakin", surname: "skywalker" }; 32 | greet.call(valueOfThis, "Mos espa", "Tatooine"); 33 | greet.apply(valueOfThis, ["Mos espa", "Tatooine"]); 34 | 35 | } 36 | -------------------------------------------------------------------------------- /Chapter15/02_ast_navigation.ts: -------------------------------------------------------------------------------- 1 | import chalk from "chalk"; 2 | import Ast, { DiagnosticMessageChain } from "ts-simple-ast"; 3 | 4 | /* 5 | This file demostrates how to use ts-simple-ast 6 | to navigate the TypeScript AST and find the name 7 | of classes 8 | */ 9 | 10 | function getAst(tsConfigPath: string, sourceFilesPath: string) { 11 | const ast = new Ast({ 12 | tsConfigFilePath: tsConfigPath, 13 | addFilesFromTsConfig: false 14 | }); 15 | ast.addExistingSourceFiles(sourceFilesPath); 16 | return ast; 17 | } 18 | 19 | const myAst = getAst("./tsconfig.json", "./app/*.ts"); 20 | const files = myAst.getSourceFiles(); 21 | 22 | const entities = files.map(f => { 23 | return { 24 | fileName: f.getFilePath(), 25 | classes: f.getClasses().map(c => c.getName()), 26 | interfaces: f.getInterfaces().map(i => i.getName()) 27 | }; 28 | }); 29 | 30 | entities.forEach(e => { 31 | console.log( 32 | chalk.cyan(` 33 | FILE: ${e.fileName}\n 34 | CLASSES: ${e.classes.length > 0 ? e.classes : "N/A"}\n 35 | INTERFACES: ${e.interfaces.length > 0 ? e.interfaces : "N/A"}\n 36 | `) 37 | ); 38 | }); 39 | -------------------------------------------------------------------------------- /Chapter11/web/frontend/components/list_group_component.tsx: -------------------------------------------------------------------------------- 1 | import * as React from "react"; 2 | import { ErrorMsg } from "./error_msg_component"; 3 | import { Loading } from "./loading_component"; 4 | 5 | interface ListGroupProps { 6 | error: Error | null; 7 | items: any[] | null; 8 | itemComponent(item: any): JSX.Element; 9 | } 10 | 11 | export class ListGroup extends React.Component { 12 | public render() { 13 | return ( 14 |
    15 | {this._renderItems()} 16 |
17 | ); 18 | } 19 | private _renderItems() { 20 | if (this.props.error) { 21 | return ; 22 | } else if (!this.props.items) { 23 | return ; 24 | } else { 25 | return this.props.items.map( 26 | (item, itemIndex) => ( 27 |
  • 28 | {this.props.itemComponent(item)} 29 |
  • 30 | ) 31 | ); 32 | } 33 | } 34 | 35 | } 36 | -------------------------------------------------------------------------------- /Chapter04/20_isp.ts: -------------------------------------------------------------------------------- 1 | namespace interface_segregation_demo_1 { 2 | 3 | interface VehicleInterface { 4 | getSpeed(): number; 5 | getVehicleType(): string; 6 | isTaxPayed(): boolean; 7 | isLightsOn(): boolean; 8 | isLightsOff(): boolean; 9 | startEngine(): void; 10 | accelerate(): number; 11 | stopEngine(): void; 12 | startRadio(): void; 13 | playCd(): void; 14 | stopRadio(): void; 15 | } 16 | 17 | } 18 | 19 | namespace interface_segregation_demo_2 { 20 | 21 | interface VehicleInterface { 22 | getSpeed(): number; 23 | getVehicleType(): string; 24 | isTaxPayed(): boolean; 25 | isLightsOn(): boolean; 26 | } 27 | 28 | interface LightsInterface { 29 | isLightsOn(): boolean; 30 | isLightsOff(): boolean; 31 | } 32 | 33 | interface RadioInterface { 34 | startRadio(): void; 35 | playCd(): void; 36 | stopRadio(): void; 37 | } 38 | 39 | interface EngineInterface { 40 | startEngine(): void; 41 | accelerate(): number; 42 | stopEngine(): void; 43 | } 44 | 45 | } 46 | -------------------------------------------------------------------------------- /Chapter11/web/frontend/components/header_component.tsx: -------------------------------------------------------------------------------- 1 | import { Link } from "react-router-dom"; 2 | import * as React from "react"; 3 | 4 | type BgColor = "primary" | "secondary" | "success" | 5 | "danger" | "warning" | "info" | "light" | 6 | "dark" | "white"; 7 | 8 | interface HeaderProps { 9 | bg: BgColor; 10 | title: string; 11 | rootPath: string; 12 | links: { path: string; text: string }[]; 13 | } 14 | 15 | export const Header = (props: HeaderProps) => ( 16 | 34 | ); 35 | -------------------------------------------------------------------------------- /Chapter06/02_function_calls.ts: -------------------------------------------------------------------------------- 1 | namespace function_calls { 2 | 3 | class Person { 4 | 5 | public name: string; 6 | public surname: string; 7 | 8 | public constructor(name: string, surname: string) { 9 | this.name = name; 10 | this.surname = surname; 11 | } 12 | 13 | public greet(city: string, country: string) { 14 | // we use the “this” operator to access name and surname 15 | let msg = `Hi, my name is ${this.name} ${this.surname}.`; 16 | msg += `I'm from ${city} (${country}).`; 17 | console.log(msg); 18 | } 19 | 20 | } 21 | 22 | const person = new Person("remo", "jansen"); 23 | person.greet("Seville", "Spain"); 24 | 25 | person.greet.call(person, "seville", "Spain"); 26 | person.greet.apply(person, ["seville", "Spain"]); 27 | 28 | person.greet.call(null, "seville", "Spain"); 29 | person.greet.apply(null, ["seville", "Spain"]); 30 | 31 | const valueOfThis = { name : "anakin", surname : "skywalker" }; 32 | person.greet.call(valueOfThis, "mos espa", "tatooine"); 33 | person.greet.apply(valueOfThis, ["mos espa", "tatooine"]); 34 | 35 | } 36 | -------------------------------------------------------------------------------- /Chapter01/11_geometry.ts: -------------------------------------------------------------------------------- 1 | namespace geometry_demo { 2 | 3 | export interface IVector2dInterface { 4 | toArray(callback: (x: number[]) => void): void; 5 | length(): number; 6 | normalize(): void; 7 | } 8 | 9 | export class Vector2d implements IVector2dInterface { 10 | private _x: number; 11 | private _y: number; 12 | constructor(x: number, y: number) { 13 | this._x = x; 14 | this._y = y; 15 | } 16 | public toArray(callback: (x: number[]) => void): void { 17 | callback([this._x, this._y]); 18 | } 19 | public length(): number { 20 | return Math.sqrt( 21 | this._x * this._x + this._y * this._y 22 | ); 23 | } 24 | public normalize() { 25 | let len = 1 / this.length(); 26 | this._x *= len; 27 | this._y *= len; 28 | } 29 | } 30 | 31 | } 32 | 33 | let vector: geometry_demo.IVector2dInterface = new geometry_demo.Vector2d(2,3); 34 | vector.normalize(); 35 | vector.toArray(function(vectorAsArray: number[]){ 36 | console.log(`x: ${vectorAsArray[0]}, y: ${vectorAsArray[1]}`); 37 | }); 38 | -------------------------------------------------------------------------------- /Chapter11/web/backend/index.ts: -------------------------------------------------------------------------------- 1 | import "reflect-metadata"; 2 | import * as express from "express"; 3 | import { Container } from "inversify"; 4 | import * as bodyParser from "body-parser"; 5 | import * as path from "path"; 6 | import { InversifyExpressServer } from "inversify-express-utils"; 7 | import { bindings } from "./inversify.config"; 8 | 9 | (async () => { 10 | 11 | try { 12 | 13 | const port = 3000; 14 | const container = new Container(); 15 | await container.loadAsync(bindings); 16 | const app = new InversifyExpressServer(container); 17 | 18 | // Declare routes of static files 19 | app.setConfig((a) => { 20 | a.use(bodyParser.json()); 21 | a.use(bodyParser.urlencoded({ extended: true })); 22 | const appPath = path.join(__dirname, "../../public"); 23 | a.use("/", express.static(appPath)); 24 | }); 25 | 26 | const server = app.build(); 27 | 28 | server.listen(port, () => { 29 | console.log(`Server running at http://127.0.0.1:${port}/`); // tslint:disable-line 30 | }); 31 | 32 | } catch (e) { 33 | console.log(e); // tslint:disable-line 34 | } 35 | 36 | })(); 37 | -------------------------------------------------------------------------------- /Chapter12/web/backend/index.ts: -------------------------------------------------------------------------------- 1 | import "reflect-metadata"; 2 | import * as express from "express"; 3 | import { Container } from "inversify"; 4 | import * as bodyParser from "body-parser"; 5 | import * as path from "path"; 6 | import { InversifyExpressServer } from "inversify-express-utils"; 7 | import { bindings } from "./inversify.config"; 8 | 9 | (async () => { 10 | 11 | try { 12 | 13 | const port = 3000; 14 | const container = new Container(); 15 | await container.loadAsync(bindings); 16 | const app = new InversifyExpressServer(container); 17 | 18 | // Declare routes of static files 19 | app.setConfig((a) => { 20 | a.use(bodyParser.json()); 21 | a.use(bodyParser.urlencoded({ extended: true })); 22 | const appPath = path.join(__dirname, "../../public"); 23 | a.use("/", express.static(appPath)); 24 | }); 25 | 26 | const server = app.build(); 27 | 28 | server.listen(port, () => { 29 | console.log(`Server running at http://127.0.0.1:${port}/`); // tslint:disable-line 30 | }); 31 | 32 | } catch (e) { 33 | console.log(e); // tslint:disable-line 34 | } 35 | 36 | })(); 37 | -------------------------------------------------------------------------------- /Chapter11/web/frontend/config/layout.tsx: -------------------------------------------------------------------------------- 1 | import { Route, Switch, BrowserRouter } from "react-router-dom"; 2 | import * as React from "react"; 3 | import { Header } from "../components/header_component"; 4 | import { HomePage } from "../pages/home_page"; 5 | import { MoviePage } from "../pages/movies_page"; 6 | import { ActorPage } from "../pages/actors_page"; 7 | import "../stores/movie_store"; 8 | import "../stores/actor_store"; 9 | 10 | export const Layout = () => ( 11 | 12 |
    13 |
    22 |
    23 | 24 | 25 | 26 | 27 | 28 |
    29 |
    30 |
    31 | ); 32 | -------------------------------------------------------------------------------- /Chapter11/web/frontend/components/button_component.tsx: -------------------------------------------------------------------------------- 1 | import * as React from "react"; 2 | 3 | type Kind = "primary" | "secondary" | "success" | 4 | "danger" | "warning" | "info" | "light" | 5 | "dark" | "link"; 6 | 7 | interface ButtonProps { 8 | kind?: Kind; 9 | onClick(): void; 10 | className?: string; 11 | } 12 | 13 | export class Button extends React.Component { 14 | public render() { 15 | return ( 16 | 23 | ); 24 | } 25 | private _getClass() { 26 | if (this.props.className !== undefined) { 27 | // If a class is specified we use it 28 | return this.props.className; 29 | } else { 30 | // If no class is specified we use the default "btn" with kind "primary" 31 | // If a kind is specified we use it 32 | const kind = this.props.kind ? this.props.kind : "primary"; 33 | return `btn btn-${kind}`; 34 | } 35 | } 36 | } 37 | -------------------------------------------------------------------------------- /Chapter03/06_rest_parameters.ts: -------------------------------------------------------------------------------- 1 | namespace rest_parameters { 2 | 3 | function add1(foo: number, bar: number, foobar: number = 0): number { 4 | return foo + bar + foobar; 5 | } 6 | 7 | function add2(...foo: number[]): number { 8 | let result = 0; 9 | for (let i = 0; i < foo.length; i++) { 10 | result += foo[i]; 11 | } 12 | return result; 13 | } 14 | 15 | add2(); // 0 16 | add2(2); // 2 17 | add2(2, 2); // 4 18 | add2(2, 2, 2); // 6 19 | add2(2, 2, 2, 2); // 8 20 | add2(2, 2, 2, 2, 2); // 10 21 | add2(2, 2, 2, 2, 2, 2); // 12 22 | 23 | function add3(foo: number[]): number { 24 | let result = 0; 25 | for (let i = 0; i < foo.length; i++) { 26 | result += foo[i]; 27 | } 28 | return result; 29 | } 30 | 31 | add3(); // Error, expected 1 arguments, but got 0. 32 | add3(2); // Error, '2' is not assignable to parameter of type 'number[]'. 33 | add3(2, 2); // Error, expected 1 arguments, but got 2. 34 | add3(2, 2, 2); // Error, expected 1 arguments, but got 3. 35 | 36 | add3([]); // returns 0 37 | add3([2]); // returns 2 38 | add3([2, 2]); // returns 4 39 | add3([2, 2, 2]); // returns 6 40 | 41 | } 42 | -------------------------------------------------------------------------------- /Chapter04/13_iterables.ts: -------------------------------------------------------------------------------- 1 | namespace itreable_demo1 { 2 | 3 | class Fib implements IterableIterator { 4 | 5 | protected fn1 = 0; 6 | protected fn2 = 1; 7 | 8 | public constructor(protected maxValue?: number) {} 9 | 10 | public next(): IteratorResult { 11 | var current = this.fn1; 12 | this.fn1 = this.fn2; 13 | this.fn2 = current + this.fn1; 14 | if (this.maxValue && current <= this.maxValue) { 15 | return { 16 | done: false, 17 | value: current 18 | }; 19 | } else { 20 | return { 21 | done: true, 22 | value: 0 23 | }; 24 | } 25 | } 26 | 27 | public [Symbol.iterator](): IterableIterator { 28 | return this; 29 | } 30 | 31 | } 32 | 33 | let fib = new Fib(); 34 | 35 | fib.next(); // { done: false, value: 0 } 36 | fib.next(); // { done: false, value: 1 } 37 | fib.next(); // { done: false, value: 1 } 38 | fib.next(); // { done: false, value: 2 } 39 | fib.next(); // { done: false, value: 3 } 40 | fib.next(); // { done: false, value: 5 } 41 | 42 | let fibMax21 = new Fib(21); 43 | 44 | for (let num of fibMax21) { 45 | console.log(num); // Prints fibonacci sequence 0 to 21 46 | } 47 | 48 | } 49 | -------------------------------------------------------------------------------- /Chapter03/16_generators.ts: -------------------------------------------------------------------------------- 1 | // Remember to enable 2 | // "lib": [ "es2015.promise", "dom", "es5", "es2015.generator", "es2015.iterable" ] 3 | // in tsconfig.json 4 | namespace generators1 { 5 | 6 | function *foo() { 7 | yield 1; 8 | yield 2; 9 | yield 3; 10 | yield 4; 11 | return 5; 12 | } 13 | 14 | let bar = foo(); 15 | bar.next(); // Object {value: 1, done: false} 16 | bar.next(); // Object {value: 2, done: false} 17 | bar.next(); // Object {value: 3, done: false} 18 | bar.next(); // Object {value: 4, done: false} 19 | bar.next(); // Object {value: 5, done: true} 20 | bar.next(); // Object { done: true } 21 | 22 | } 23 | 24 | namespace generators2 { 25 | 26 | function* foo() { 27 | let i = 1; 28 | while (true) { 29 | yield i++; 30 | } 31 | } 32 | 33 | let bar = foo(); 34 | bar.next(); // Object {value: 1, done: false} 35 | bar.next(); // Object {value: 2, done: false} 36 | bar.next(); // Object {value: 3, done: false} 37 | bar.next(); // Object {value: 4, done: false} 38 | bar.next(); // Object {value: 5, done: false} 39 | bar.next(); // Object {value: 6, done: false} 40 | bar.next(); // Object {value: 7, done: false} 41 | 42 | } 43 | -------------------------------------------------------------------------------- /Chapter02/06_intersection_types.ts: -------------------------------------------------------------------------------- 1 | // Don't forget to disable strictNullChecks or strict in tsconfig.json 2 | namespace intersection_types_demo { 3 | 4 | interface A { a: string } 5 | interface B { b: string } 6 | interface C { c: string } 7 | declare let abc: A & B & C; 8 | abc.a = "hello"; // OK 9 | abc.b = "hello"; // OK 10 | abc.c = "hello"; // OK 11 | abc.d = "hello"; // Error 12 | 13 | interface X { x: A } 14 | interface Y { x: B } 15 | interface Z { x: C } 16 | declare let xyz: X & Y & Z; 17 | xyz.x.a = "hello"; // OK 18 | xyz.x.b = "hello"; // OK 19 | xyz.x.c = "hello"; // OK 20 | xyz.x.d = "hello"; // Error 21 | 22 | type F1 = (x: string) => string; 23 | type F2 = (x: number) => number; 24 | declare let f: F1 & F2; 25 | let s = f("hello"); // OK 26 | let n = f(42); // OK 27 | let t = f(true); // Error 28 | 29 | interface Supplier { 30 | orderItems(): void; 31 | getAddress(): void; 32 | } 33 | 34 | interface Customer { 35 | sellItems(): void; 36 | getAddress(): void; 37 | } 38 | 39 | declare let person: Supplier & Customer; 40 | person.getAddress(); // OK 41 | person.orderItems(); // OK 42 | person.sellItems(); // OK 43 | 44 | } 45 | -------------------------------------------------------------------------------- /Chapter05/09_inversion_vs_injection.ts: -------------------------------------------------------------------------------- 1 | namespace dependency_injection_demo { 2 | 3 | class Katana { 4 | public tryHit(fromDistance: number) { 5 | return fromDistance <= 2; 6 | } 7 | } 8 | 9 | class Ninja { 10 | public constructor( 11 | private _katana: Katana 12 | ) {} 13 | public fight(fromDistance: number) { 14 | return this._katana.tryHit(fromDistance); 15 | } 16 | } 17 | 18 | const ninja = new Ninja(new Katana()); 19 | ninja.fight(2); // true 20 | ninja.fight(5); // false 21 | 22 | } 23 | 24 | namespace dependency_inversion_demo { 25 | 26 | interface Weapon { 27 | tryHit(fromDistance: number): boolean; 28 | } 29 | 30 | class Katana implements Weapon { 31 | public tryHit(fromDistance: number) { 32 | return fromDistance <= 2; 33 | } 34 | } 35 | 36 | class Ninja { 37 | public constructor( 38 | private _weapon: Weapon 39 | ) {} 40 | public fight(fromDistance: number) { 41 | return this._weapon.tryHit(fromDistance); 42 | } 43 | } 44 | 45 | const ninja = new Ninja(new Katana()); 46 | ninja.fight(2); // true 47 | ninja.fight(5); // false 48 | 49 | } 50 | -------------------------------------------------------------------------------- /Chapter12/web/frontend/components/textfield.component.ts: -------------------------------------------------------------------------------- 1 | import { Component, EventEmitter, Input, Output } from "@angular/core"; 2 | 3 | @Component({ 4 | selector: "app-text-field", 5 | template: ` 6 |
    7 |
    8 | 9 |
    10 |
    11 | 12 | 19 |
    20 |
    21 | ` 22 | }) 23 | export class TextFieldComponent { 24 | 25 | @Input() public title!: string; 26 | @Input() public id!: string; 27 | @Input() public placeholder!: string; 28 | @Input() public errorMsg!: null | string; 29 | @Output() public onChange = new EventEmitter<{k: string; v: string}>(); 30 | 31 | public onEdit(event: any) { 32 | const value = (event.target as any).value; 33 | const key = (event.target as any).id; 34 | this.onChange.emit({ v: value, k: key }); 35 | } 36 | 37 | } 38 | -------------------------------------------------------------------------------- /Chapter07/04_higher_order_function.ts: -------------------------------------------------------------------------------- 1 | namespace higher_oder_function_demo_1 { 2 | 3 | function addDelay(func: () => void, ms: number) { 4 | setTimeout(() => { 5 | func(); 6 | }, ms); 7 | } 8 | 9 | function sayHello() { 10 | console.log("Hello world!"); 11 | } 12 | 13 | addDelay(sayHello, 500); // Prints "Hello world!" (after 500 ms) 14 | 15 | } 16 | 17 | namespace higher_oder_function_demo_2 { 18 | 19 | function addDelay(msg: string, ms: number) { 20 | return () => { 21 | setTimeout(() => { 22 | console.log(msg); 23 | }, ms); 24 | }; 25 | } 26 | 27 | const delayedSayHello = addDelay("Hello world!", 500); 28 | delayedSayHello(); // Prints "Hello world!" (after 500 ms) 29 | 30 | } 31 | 32 | namespace higher_oder_function_demo_3 { 33 | 34 | function addDelay(func: () => void, ms: number) { 35 | return () => { 36 | setTimeout(() => { 37 | func(); 38 | }, ms); 39 | }; 40 | } 41 | 42 | function sayHello() { 43 | console.log("Hello world!"); 44 | } 45 | 46 | const delayedSayHello = addDelay(sayHello, 500); 47 | delayedSayHello(); // Prints "Hello world!" (after 500 ms) 48 | 49 | } 50 | -------------------------------------------------------------------------------- /Chapter12/web/frontend/components/button.component.ts: -------------------------------------------------------------------------------- 1 | import { Component, Attribute, Input, Output, EventEmitter } from "@angular/core"; 2 | 3 | type Kind = "primary" | "secondary" | "success" | 4 | "danger" | "warning" | "info" | "light" | 5 | "dark" | "link"; 6 | 7 | @Component({ 8 | selector: "app-button", 9 | template: ` 10 | ` 17 | }) 18 | export class ButtonComponent { 19 | 20 | public btnClass: string; 21 | @Output() public clicked = new EventEmitter(); 22 | 23 | public constructor( 24 | @Attribute("kind") kind: Kind, 25 | @Attribute("className") className: string 26 | ) { 27 | this.btnClass = this._getBtnClass(kind, className); 28 | } 29 | 30 | private _getBtnClass(kind: Kind | null, className: string | null) { 31 | if (className) { 32 | return className; 33 | } else { 34 | const actualKind = kind ? kind : "primary"; 35 | return `btn btn-${actualKind}`; 36 | } 37 | } 38 | 39 | public click() { 40 | this.clicked.emit(); 41 | } 42 | 43 | } 44 | -------------------------------------------------------------------------------- /Chapter11/web/frontend/components/textfield_component.tsx: -------------------------------------------------------------------------------- 1 | import * as React from "react"; 2 | import { ErrorMsg } from "./error_msg_component"; 3 | 4 | interface TextFieldProps { 5 | title: string; 6 | id: string; 7 | placeholder: string; 8 | value: any; 9 | isValid(value: any): boolean; 10 | onChange(value: string): void; 11 | } 12 | 13 | export class TextField extends React.Component { 14 | public render() { 15 | return ( 16 |
    17 | {this._renderError()} 18 |
    19 | 20 | this.props.onChange((e.target as any).value)} 26 | /> 27 |
    28 |
    29 | ); 30 | } 31 | private _renderError() { 32 | if (!this.props.isValid(this.props.value)) { 33 | return ( 34 | 35 | ); 36 | } 37 | } 38 | } 39 | -------------------------------------------------------------------------------- /Chapter02/25_mapped_types.ts: -------------------------------------------------------------------------------- 1 | namespace mapped_types_demo { 2 | 3 | type Keyify = { 4 | [P in keyof T]: P; 5 | }; 6 | 7 | function getKeys(obj: T): Keyify { 8 | const keysArr = Object.keys(obj); 9 | const stringifyObj = keysArr.reduce((p, c, i, a) => { 10 | return { 11 | ...p, 12 | [c]: c 13 | }; 14 | }, {}); 15 | return stringifyObj as Keyify; 16 | } 17 | 18 | interface User { 19 | name: string; 20 | age: number; 21 | } 22 | 23 | let user: User = { name: "Remo", age: 28 }; 24 | let keys = getKeys(user); 25 | 26 | console.log(keys.name); // "name" 27 | console.log(keys.age); // "age" 28 | 29 | // Make all properties in T optional 30 | type Partial = { 31 | [P in keyof T]?: T[P]; 32 | }; 33 | 34 | // Make all properties in T readonly 35 | type Readonly = { 36 | readonly [P in keyof T]: T[P]; 37 | }; 38 | 39 | // From T pick a set of properties K 40 | type Pick = { 41 | [P in K]: T[P]; 42 | }; 43 | 44 | // Construct a type with a set of properties K of type T 45 | type Record = { 46 | [P in K]: T; 47 | }; 48 | 49 | } 50 | -------------------------------------------------------------------------------- /Chapter04/17_encapsulation.ts: -------------------------------------------------------------------------------- 1 | namespace encapsulation_demo { 2 | 3 | class Email { 4 | private _email: string; 5 | public constructor(email: string) { 6 | if (this._validateEmail(email)) { 7 | this._email = email; 8 | } else { 9 | throw new Error("Invalid email!"); 10 | } 11 | } 12 | public toString(): string { 13 | return this._email; 14 | } 15 | private _validateEmail(email: string) { 16 | const re = /\S+@\S+\.\S+/; 17 | return re.test(email); 18 | } 19 | } 20 | 21 | class Person { 22 | public name: string; 23 | public surname: string; 24 | public email: Email; 25 | public constructor( 26 | name: string, surname: string, email: Email 27 | ) { 28 | this.email = email; 29 | this.name = name; 30 | this.surname = surname; 31 | } 32 | public greet() { 33 | console.log( 34 | `Hi! I'm ${this.name}, 35 | you can reach me at ${this.email.toString()}` 36 | ); 37 | } 38 | } 39 | 40 | let person: Person = new Person( 41 | "Remo", 42 | "Jansen", 43 | new Email("remo.jansen@wolksoftware.com") 44 | ); 45 | 46 | } 47 | -------------------------------------------------------------------------------- /Chapter09/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "chapter_09", 3 | "version": "1.0.0", 4 | "description": "Learning TypeScript: Chapter 09 Source code", 5 | "main": "index.js", 6 | "scripts": { 7 | "test": "npm run lint & npm run nyc", 8 | "ts-node": "ts-node ./src/main_server.ts", 9 | "lint": "tslint --project tsconfig.json -c tslint.json ./**/*.ts", 10 | "nyc": "nyc --clean --all -x webpack.config.js -x public -x dist -x globals.js --require ts-node/register --extension .ts -- mocha --timeout 5000 **/*.test.ts" 11 | }, 12 | "keywords": [], 13 | "author": "Remo H. Jansen (http://www.remojansen.com)", 14 | "license": "MIT", 15 | "devDependencies": { 16 | "@types/chai": "4.1.0", 17 | "@types/mocha": "2.2.46", 18 | "awesome-typescript-loader": "3.4.1", 19 | "chai": "4.1.2", 20 | "css-loader": "0.28.8", 21 | "extract-text-webpack-plugin": "3.0.2", 22 | "gulp": "3.9.1", 23 | "gulp-tslint": "8.1.2", 24 | "mocha": "4.1.0", 25 | "node-sass": "4.7.2", 26 | "nyc": "11.4.1", 27 | "resolve-url-loader": "2.2.1", 28 | "sass-loader": "6.0.6", 29 | "style-loader": "0.19.1", 30 | "ts-node": "5.0.1", 31 | "tslint": "5.9.1", 32 | "typescript": "2.8.1", 33 | "webpack": "3.10.0", 34 | "webpack-dev-server": "2.11.0" 35 | }, 36 | "dependencies": { 37 | "chalk": "2.3.0" 38 | } 39 | } 40 | -------------------------------------------------------------------------------- /Chapter04/14_abstract_classes.ts: -------------------------------------------------------------------------------- 1 | namespace abstract_classes { 2 | 3 | abstract class Department { 4 | 5 | constructor(public name: string) { 6 | } 7 | 8 | public printName(): void { 9 | console.log("Department name: " + this.name); 10 | } 11 | 12 | public abstract printMeeting(): void; // must be implemented in derived classes 13 | } 14 | 15 | class AccountingDepartment extends Department { 16 | 17 | public constructor() { 18 | super("Accounting and Auditing"); // constructors in derived classes must call super() 19 | } 20 | 21 | public printMeeting(): void { 22 | console.log("The Accounting Department meets each Monday at 10am."); 23 | } 24 | 25 | public generateReports(): void { 26 | console.log("Generating accounting reports..."); 27 | } 28 | } 29 | 30 | // OK: Create a reference to an abstract type 31 | let department: Department; 32 | 33 | // Error: cannot create an instance of an abstract class 34 | department = new Department(); 35 | 36 | // OK: Create and assign a non-abstract subclass 37 | department = new AccountingDepartment(); 38 | department.printName(); 39 | department.printMeeting(); 40 | 41 | // Error: Method doesn't exist on declared abstract type 42 | department.generateReports(); 43 | 44 | } 45 | -------------------------------------------------------------------------------- /Chapter10/09_mvc/controllers/movie_controller.ts: -------------------------------------------------------------------------------- 1 | import { Router } from "express"; 2 | import { getRepository } from "../repositories/movie_repository"; 3 | 4 | const movieRouter = Router(); 5 | 6 | movieRouter.get("/", function (req, res) { 7 | const movieRepository = getRepository(); 8 | movieRepository.find().then((movies) => { 9 | res.json(movies); 10 | }).catch((e: Error) => { 11 | res.status(500); 12 | res.send(e.message); 13 | }); 14 | }); 15 | 16 | movieRouter.get("/:year", function (req, res) { 17 | const movieRepository = getRepository(); 18 | movieRepository.find({ 19 | year: req.params.year 20 | }).then((movies) => { 21 | res.json(movies); 22 | }).catch((e: Error) => { 23 | res.status(500); 24 | res.send(e.message); 25 | }); 26 | }); 27 | 28 | movieRouter.post("/", function (req, res) { 29 | const movieRepository = getRepository(); 30 | const newMovie = req.body; 31 | if ( 32 | typeof newMovie.title !== "string" || 33 | typeof newMovie.year !== "number" 34 | ) { 35 | res.status(400); 36 | res.send(`Invalid Movie!`); 37 | } 38 | movieRepository.find(newMovie).then((movie) => { 39 | res.json(movie); 40 | }).catch((e: Error) => { 41 | res.status(500); 42 | res.send(e.message); 43 | }); 44 | }); 45 | 46 | export { movieRouter }; 47 | -------------------------------------------------------------------------------- /Chapter04/09_method_overriding.ts: -------------------------------------------------------------------------------- 1 | namespace method_overriding { 2 | 3 | class Person { 4 | public constructor( 5 | public name: string, 6 | public surname: string, 7 | public email: string 8 | ) {} 9 | public greet() { 10 | console.log("Hi!"); 11 | } 12 | } 13 | 14 | class Teacher extends Person { 15 | public constructor( 16 | name: string, 17 | surname: string, 18 | email: string, 19 | public subjects: string[] 20 | ) { 21 | super(name, surname, email); 22 | this.subjects = subjects; 23 | } 24 | public greet() { 25 | super.greet(); 26 | console.log("I teach " + this.subjects.join(" & ")); 27 | } 28 | public teach() { 29 | console.log("Welcome to class!"); 30 | } 31 | } 32 | 33 | const person = new Person( 34 | "Remo", 35 | "Jansen", 36 | "remo.jansen@wolksoftware.com" 37 | ); 38 | 39 | const teacher = new Teacher( 40 | "Remo", 41 | "Jansen", 42 | "remo.jansen@wolksoftware.com", 43 | ["math", "physics"] 44 | ); 45 | 46 | person.greet(); // "Hi!" 47 | teacher.greet(); // "Hi! I teach math & physics" 48 | person.teach(); // Error 49 | teacher.teach(); // "Welcome to class!" 50 | 51 | } 52 | -------------------------------------------------------------------------------- /Chapter11/web/frontend/components/grid_component.tsx: -------------------------------------------------------------------------------- 1 | import * as React from "react"; 2 | 3 | export class Container extends React.Component { 4 | public render() { 5 | return ( 6 |
    7 | {this.props.children} 8 |
    9 | ); 10 | } 11 | } 12 | 13 | export class Row extends React.Component { 14 | public render() { 15 | return ( 16 |
    17 | {this.props.children} 18 |
    19 | ); 20 | } 21 | } 22 | 23 | // In the bootstrap grid system the max size is 12 24 | type ColumnWidth = 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12; 25 | type DeviceSize = "s" | "m" | "l" | "xl"; 26 | 27 | interface ColumnProps { 28 | width: ColumnWidth; 29 | size?: DeviceSize; 30 | style?: React.CSSProperties; 31 | } 32 | 33 | export class Column extends React.Component { 34 | public render() { 35 | return ( 36 |
    37 | {this.props.children} 38 |
    39 | ); 40 | } 41 | private _getClass() { 42 | if (this.props.size !== undefined) { 43 | return `col-${this.props.size}-${this.props.width}`; 44 | } else { 45 | return `col-${this.props.width}`; 46 | } 47 | } 48 | } 49 | -------------------------------------------------------------------------------- /Chapter12/web/frontend/components/header.component.ts: -------------------------------------------------------------------------------- 1 | import { Component, Input, Attribute } from "@angular/core"; 2 | 3 | type BgColor = "primary" | "secondary" | "success" | 4 | "danger" | "warning" | "info" | "light" | 5 | "dark" | "white"; 6 | 7 | export interface Route { 8 | label: string; 9 | path: string; 10 | } 11 | 12 | @Component({ 13 | selector: "app-header", 14 | template: ` 15 | ` 27 | }) 28 | export class HeaderComponent { 29 | 30 | public navClass!: string; 31 | public title!: string; 32 | public rootPath!: string; 33 | @Input() public links!: Route[]; 34 | 35 | public constructor( 36 | @Attribute("bg") bg: BgColor, 37 | @Attribute("title") title: string, 38 | @Attribute("rootPath") rootPath: string, 39 | ) { 40 | this.navClass = `navbar navbar-expand-lg navbar-light bg-${bg}`; 41 | this.title = title; 42 | this.rootPath = rootPath; 43 | } 44 | } 45 | -------------------------------------------------------------------------------- /Chapter11/web/frontend/interfaces.ts: -------------------------------------------------------------------------------- 1 | import { ActorInterface } from "../universal/entities/actor"; 2 | import { MovieInterface } from "../universal/entities/movie"; 3 | 4 | export type Status = "idle" | "pending" | "error" | "done"; 5 | 6 | export interface ActorStore { 7 | actors: ActorInterface[]; 8 | loadStatus: Status; 9 | saveStatus: Status; 10 | deleteStatus: Status; 11 | deleteActorId: null | number; 12 | editorValue: null | Partial; 13 | getAll(): Promise; 14 | create(actor: ActorInterface): Promise; 15 | edit(k: K, val: T[K]): void; 16 | delete(id: number): Promise; 17 | focusEditor(): void; 18 | focusDeleteDialog(id: number): void; 19 | focusOutDeleteDialog(): void; 20 | focusOutEditor(): void; 21 | } 22 | 23 | export interface MovieStore { 24 | movies: MovieInterface[]; 25 | loadStatus: Status; 26 | saveStatus: Status; 27 | deleteStatus: Status; 28 | deleteMovieId: null | number; 29 | editorValue: null | Partial; 30 | getAll(): Promise; 31 | create(movie: MovieInterface): Promise; 32 | edit(k: K, val: T[K]): void; 33 | delete(id: number): Promise; 34 | focusEditor(): void; 35 | focusDeleteDialog(id: number): void; 36 | focusOutDeleteDialog(): void; 37 | focusOutEditor(): void; 38 | } 39 | -------------------------------------------------------------------------------- /Chapter12/web/frontend/components/grid.component.ts: -------------------------------------------------------------------------------- 1 | import { Component, Attribute } from "@angular/core"; 2 | 3 | // In the bootstrap grid system the max size is 12 4 | type ColumnWidth = 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12; 5 | type DeviceSize = "sm" | "md" | "lg" | "xl"; 6 | 7 | @Component({ 8 | selector: "app-container", 9 | template: ` 10 |
    11 | 12 |
    13 | ` 14 | }) 15 | export class ContainerComponent {} 16 | 17 | @Component({ 18 | host: { 19 | "[class]": "'row'" 20 | }, 21 | selector: "app-row", 22 | template: ` 23 | 24 | ` 25 | }) 26 | export class RowComponent {} 27 | 28 | @Component({ 29 | host: { 30 | "[class]": "columnClass" 31 | }, 32 | selector: "app-column", 33 | template: ` 34 | 35 | ` 36 | }) 37 | export class ColumnComponent { 38 | 39 | public columnClass: string; 40 | 41 | public constructor( 42 | @Attribute("width") width: ColumnWidth, 43 | @Attribute("size") size: DeviceSize | null, 44 | ) { 45 | this.columnClass = this._getClass(width, size); 46 | } 47 | 48 | private _getClass(width: ColumnWidth, size: DeviceSize | null) { 49 | if (size) { 50 | return `col-${size}-${width}`; 51 | } else { 52 | return `col-${width}`; 53 | } 54 | } 55 | } 56 | -------------------------------------------------------------------------------- /Chapter07/05_composition.ts: -------------------------------------------------------------------------------- 1 | namespace composition_demo_1 { 2 | const trim = (s: string) => s.trim(); 3 | const capitalize = (s: string) => s.toUpperCase(); 4 | const trimAndCapitalize = (s: string) => capitalize(trim(s)); 5 | trimAndCapitalize(" hello world "); // "HELLO WORLD" 6 | } 7 | 8 | namespace composition_demo_2 { 9 | const compose = (f: (x: T) => T, g: (x: T) => T) => (x: T) => f(g(x)); 10 | const trim = (s: string) => s.trim(); 11 | const capitalize = (s: string) => s.toUpperCase(); 12 | const trimAndCapitalize = compose(trim, capitalize); 13 | trimAndCapitalize(" hello world "); // "HELLO WORLD" 14 | } 15 | 16 | namespace composition_demo_3 { 17 | const compose = (f: (x: T2) => T3, g: (x: T1) => T2) => (x: T1) => f(g(x)); 18 | 19 | const compose3 = ( 20 | f: (x: T3) => T4, 21 | g: (x: T2) => T3, 22 | h: (x: T1) => T2 23 | ) => (x: T1) => f(g(h(x))); 24 | 25 | } 26 | 27 | namespace composition_demo_4 { 28 | 29 | const compose = (...functions: Array<(arg: any) => any>) => 30 | (arg: any) => 31 | functions.reduce((prev, curr) => { 32 | return curr(prev); 33 | }, arg); 34 | 35 | const trim = (s: string) => s.trim(); 36 | const capitalize = (s: string) => s.toUpperCase(); 37 | 38 | const trimAndCapitalize = compose(trim, capitalize); 39 | trimAndCapitalize(" hello world "); // "HELLO WORLD" 40 | 41 | } 42 | -------------------------------------------------------------------------------- /Chapter14/02_testing/src/backend/server.ts: -------------------------------------------------------------------------------- 1 | import * as express from "express"; 2 | import * as path from "path"; 3 | import { MathDemo } from "./math_demo"; 4 | 5 | export function getApp() { 6 | 7 | const app = express(); 8 | const nullableAppRootPath = process.env.PWD || process.cwd(); 9 | 10 | if (!nullableAppRootPath) { 11 | throw new Error("Please run the app via npm scripts"); 12 | } 13 | 14 | const appRootPath = nullableAppRootPath; 15 | 16 | // Route for static assests (css and js file) 17 | // the path is ../../../ because it is executed 18 | // from the /dist/backend folder 19 | // /Users/remojansen/CODE/LearningTypeScript/chapters/chapter_11/02_frontend_testing 20 | console.log(nullableAppRootPath); // tslint:disable-line 21 | app.use("/public", express.static(path.join(appRootPath, "public"))); 22 | 23 | // Route for index.html the path 24 | // is ../../../ because it is executed 25 | // from the /dist/backend folder 26 | app.get("/", (req, res) => res.sendFile(path.join(appRootPath, "index.html"))); 27 | 28 | // Route for math pow operation 29 | app.get("/api/math/pow/:base/:exponent", (req, res) => { 30 | const mathDemo = new MathDemo(); 31 | const base = parseInt(req.params.base, 10); 32 | const exponent = parseInt(req.params.exponent, 10); 33 | const result = mathDemo.pow(base, exponent); 34 | res.json({ result }); 35 | }); 36 | 37 | return app; 38 | 39 | } 40 | -------------------------------------------------------------------------------- /Chapter04/21_lsp.ts: -------------------------------------------------------------------------------- 1 | namespace liskov_substitution_principle { 2 | 3 | interface PersistanceServiceInterface { 4 | save(value: string): string; 5 | } 6 | 7 | function getUniqueId() { 8 | return Math.random().toString(36).substr(2, 9); 9 | } 10 | 11 | class CookiePersitanceService implements PersistanceServiceInterface { 12 | public save(value: string): string { 13 | let id = getUniqueId(); 14 | document.cookie = `${id}=${value}`; 15 | return id; 16 | } 17 | } 18 | 19 | class FavouritesController { 20 | private _persistanceService: PersistanceServiceInterface; 21 | public constructor(persistanceService: PersistanceServiceInterface) { 22 | this._persistanceService = persistanceService; 23 | } 24 | public saveAsFavourite(articleId: string) { 25 | return this._persistanceService.save(articleId); 26 | } 27 | } 28 | 29 | const favController1 = new FavouritesController( 30 | new CookiePersitanceService() 31 | ); 32 | 33 | class LocalStoragePersitanceService implements PersistanceServiceInterface { 34 | public save(value: string): string { 35 | const id = getUniqueId(); 36 | localStorage.setItem(`${id}`, value); 37 | return id; 38 | } 39 | } 40 | 41 | const favController = new FavouritesController( 42 | new LocalStoragePersitanceService() 43 | ); 44 | 45 | } 46 | -------------------------------------------------------------------------------- /Chapter02/24_generic_constraints.ts: -------------------------------------------------------------------------------- 1 | interface Comparable { 2 | equals(value: T): boolean; 3 | } 4 | 5 | function isEqual>(comparable: T, value: TVal) { 6 | return comparable.equals(value); 7 | } 8 | 9 | interface RectangleInterface { 10 | width: number; 11 | height: number; 12 | } 13 | 14 | type ComparableRectangle = RectangleInterface & Comparable; 15 | 16 | class Rectangle implements ComparableRectangle { 17 | public width: number; 18 | public height: number; 19 | public constructor(width: number, height: number) { 20 | this.width = width; 21 | this.height = height; 22 | } 23 | public equals(value: Rectangle) { 24 | return value.width === this.width && value.height === this.height; 25 | } 26 | }; 27 | 28 | interface CircleInterface { 29 | radius: number; 30 | } 31 | 32 | type ComparableCircle = CircleInterface & Comparable; 33 | 34 | class Circle implements ComparableCircle { 35 | public radius: number; 36 | public constructor(radius: number) { 37 | this.radius = radius 38 | } 39 | public equals(value: CircleInterface): boolean { 40 | return value.radius === this.radius; 41 | } 42 | } 43 | 44 | const circle = new Circle(5); 45 | const rectangle = new Rectangle(5, 8); 46 | 47 | isEqual(rectangle, { width: 5, height: 8 }); 48 | isEqual(circle, { radius: 5 }); 49 | -------------------------------------------------------------------------------- /Chapter03/20_delegate_generators.ts: -------------------------------------------------------------------------------- 1 | // Remember to enable 2 | // "lib": [ "es2015.promise", "dom", "es5", "es2015.generator", "es2015.iterable" ] 3 | // "downlevelIteration": true 4 | // in tsconfig.json 5 | namespace delegate_generators_demo_1 { 6 | 7 | function* g1() { 8 | yield 2; 9 | yield 3; 10 | yield 4; 11 | } 12 | 13 | function* g2() { 14 | yield 1; 15 | yield* g1(); 16 | yield 5; 17 | } 18 | 19 | var iterator1 = g2(); 20 | 21 | console.log(iterator1.next()); // {value: 1, done: false} 22 | console.log(iterator1.next()); // {value: 2, done: false} 23 | console.log(iterator1.next()); // {value: 3, done: false} 24 | console.log(iterator1.next()); // {value: 4, done: false} 25 | console.log(iterator1.next()); // {value: 5, done: false} 26 | console.log(iterator1.next()); // {value: undefined, done: true} 27 | 28 | } 29 | 30 | namespace delegate_generators_demo_2 { 31 | 32 | function* g1() { 33 | yield 1; 34 | yield* [2, 3, 4]; 35 | yield 5; 36 | } 37 | 38 | var iterator = g1(); 39 | 40 | console.log(iterator.next()); // {value: 1, done: false} 41 | console.log(iterator.next()); // {value: 2, done: false} 42 | console.log(iterator.next()); // {value: 3, done: false} 43 | console.log(iterator.next()); // {value: 4, done: false} 44 | console.log(iterator.next()); // {value: 5, done: false} 45 | console.log(iterator.next()); // {value: undefined, done: true} 46 | 47 | } 48 | -------------------------------------------------------------------------------- /Chapter04/08_readonly_properties.ts: -------------------------------------------------------------------------------- 1 | namespace readonly_properties_demo_1 { 2 | 3 | class Vector3 { 4 | 5 | public constructor( 6 | public readonly x: number, 7 | public readonly y: number, 8 | public readonly z: number 9 | ) {} 10 | 11 | public length() { 12 | return Math.sqrt( 13 | this.x * this.x + 14 | this.y * this.y + 15 | this.z * this.z 16 | ); 17 | } 18 | 19 | public normalize() { 20 | let len = 1 / this.length(); 21 | this.x *= len; // Error 22 | this.y *= len; // Error 23 | this.z *= len; // Error 24 | } 25 | 26 | } 27 | 28 | } 29 | 30 | namespace readonly_properties_demo_2 { 31 | 32 | class Vector3 { 33 | 34 | public constructor( 35 | public readonly x: number, 36 | public readonly y: number, 37 | public readonly z: number 38 | ) {} 39 | 40 | public length() { 41 | return Math.sqrt( 42 | this.x * this.x + 43 | this.y * this.y + 44 | this.z * this.z 45 | ); 46 | } 47 | 48 | public normalize() { 49 | let len = 1 / this.length(); 50 | return new Vector3( 51 | this.x * len, // OK 52 | this.y * len, // OK 53 | this.z * len // OK 54 | ); 55 | } 56 | 57 | } 58 | 59 | } 60 | -------------------------------------------------------------------------------- /Chapter14/02_testing/src/frontend/numeric_input_component.tsx: -------------------------------------------------------------------------------- 1 | import * as React from "react"; 2 | 3 | interface NumericInputProps { 4 | id: string; 5 | name: string; 6 | value: string; 7 | onChangeHandler(val: string): void; 8 | } 9 | 10 | export class NumericInput extends React.Component { 11 | 12 | public constructor(props: NumericInputProps) { 13 | super(props); 14 | } 15 | 16 | public render() { 17 | return ( 18 |
    19 | {this._renderError()} 20 | 21 | { 27 | const val = e.target.value as any; 28 | this.props.onChangeHandler(val); 29 | }} 30 | /> 31 |
    32 | ); 33 | } 34 | 35 | private _idValid() { 36 | const val = this.props.value as any; 37 | return (val && !isNaN(val)); 38 | } 39 | 40 | private _renderError() { 41 | if (!this._idValid()) { 42 | return ( 43 |
    44 |

    {`${this.props.name} must be numeric!`}

    45 |
    46 | ); 47 | } 48 | } 49 | 50 | } 51 | -------------------------------------------------------------------------------- /Chapter04/02_Inheritance.ts: -------------------------------------------------------------------------------- 1 | namespace inheritance { 2 | 3 | class Person { 4 | public name: string; 5 | public surname: string; 6 | public email: string; 7 | public constructor(name: string, surname: string, email: string) { 8 | this.email = email; 9 | this.name = name; 10 | this.surname = surname; 11 | } 12 | public greet() { 13 | console.log("Hi!"); 14 | } 15 | } 16 | 17 | class Teacher extends Person { 18 | public teach() { 19 | console.log("Welcome to class!"); 20 | } 21 | } 22 | 23 | const person = new Person( 24 | "Remo", 25 | "Jansen", 26 | "remo.jansen@wolksoftware.com" 27 | ); 28 | 29 | const teacher = new Teacher( 30 | "Remo", 31 | "Jansen", 32 | "remo.jansen@wolksoftware.com" 33 | ); 34 | 35 | person.greet(); // "Hi!" 36 | teacher.greet(); // "Hi!" 37 | person.teach(); // Error : Property 'teach' does not exist on type 'Person' 38 | teacher.teach(); // "Welcome to class!" 39 | 40 | class SchoolPrincipal extends Teacher { 41 | public manageTeachers() { 42 | return console.log("We need to help our students!"); 43 | } 44 | } 45 | 46 | const principal = new SchoolPrincipal( 47 | "Remo", 48 | "Jansen", 49 | "remo.jansen@wolksoftware.com" 50 | ); 51 | 52 | principal.greet(); // "Hi!" 53 | principal.teach(); // "Welcome to class!" 54 | principal.manageTeachers(); // "We need to help our students!" 55 | 56 | } 57 | -------------------------------------------------------------------------------- /Chapter04/06_static_members.ts: -------------------------------------------------------------------------------- 1 | namespace static_classes_demo_1 { 2 | 3 | class TemperatureConverter { 4 | 5 | public static CelsiusToFahrenheit( 6 | celsiusTemperature: number 7 | ) { 8 | return (celsiusTemperature * 9 / 5) + 32; 9 | } 10 | 11 | public static FahrenheitToCelsius( 12 | fahrenheitTemperature: number 13 | ) { 14 | return (fahrenheitTemperature - 32) * 5 / 9; 15 | } 16 | 17 | } 18 | 19 | let fahrenheit = 100; 20 | let celsius = TemperatureConverter.FahrenheitToCelsius(fahrenheit); 21 | fahrenheit = TemperatureConverter.CelsiusToFahrenheit(celsius); 22 | 23 | } 24 | 25 | namespace static_class_demo_2 { 26 | 27 | class Vector3 { 28 | 29 | public static GetDefault() { 30 | return new Vector3(0, 0, 0); 31 | } 32 | 33 | public constructor( 34 | private _x: number, 35 | private _y: number, 36 | private _z: number 37 | ) {} 38 | 39 | public length() { 40 | return Math.sqrt( 41 | this._x * this._x + 42 | this._y * this._y + 43 | this._z * this._z 44 | ); 45 | } 46 | 47 | public normalize() { 48 | let len = 1 / this.length(); 49 | this._x *= len; 50 | this._y *= len; 51 | this._z *= len; 52 | } 53 | 54 | } 55 | 56 | const vector1 = Vector3.GetDefault(); 57 | vector1.normalize(); 58 | 59 | const vector2 = new Vector3(1, 1, 1); 60 | vector2.normalize(); 61 | 62 | } 63 | -------------------------------------------------------------------------------- /Chapter07/07_currying.ts: -------------------------------------------------------------------------------- 1 | namespace currying_demo_1 { 2 | 3 | function curry2(fn: (a: T1, b: T2) => T3) { 4 | return (a: T1) => (b: T2) => fn(a, b); 5 | } 6 | 7 | function add(a: number, b: number) { 8 | return a + b; 9 | } 10 | 11 | const curriedAdd = curry2(add); 12 | const add5 = curriedAdd(5); 13 | const addResult = add5(5); 14 | console.log(addResult); // 10 15 | 16 | function multiply(a: number, b: number) { 17 | return a * b; 18 | } 19 | 20 | const curriedMultiply = curry2(multiply); 21 | const multiplyBy5 = curriedMultiply(5); 22 | const multiplyResult = multiplyBy5(5); 23 | console.log(multiplyResult); // 25 24 | 25 | } 26 | 27 | namespace currying_demo_2 { 28 | 29 | function curry3(fn: (a: T1, b: T2, c: T3) => T4) { 30 | return (a: T1) => (b: T2) => (c: T3) => fn(a, b, c); 31 | } 32 | 33 | const compose = (...functions: Array<(arg: any) => any>) => 34 | (arg: any) => 35 | functions.reduce((prev, curr) => { 36 | return curr(prev); 37 | }, arg); 38 | 39 | const trim = (s: string) => s.trim(); 40 | const capitalize = (s: string) => s.toUpperCase(); 41 | const trimAndCapitalize = compose(trim, capitalize); 42 | 43 | const replace = (s: string, f: string, r: string) => 44 | s.split(f).join(r); 45 | 46 | const curriedReplace = curry3(replace); 47 | const trimCapitalizeAndReplace = compose( 48 | trimAndCapitalize, 49 | curriedReplace("/")("-") 50 | ); 51 | 52 | trimAndCapitalize(" 13/feb/1989 "); // "13-FEB-1989" 53 | 54 | } 55 | -------------------------------------------------------------------------------- /Chapter03/07_function_overloading.ts: -------------------------------------------------------------------------------- 1 | namespace function_overloading { 2 | 3 | function test(name: string): string; // overloaded signature 4 | function test(age: number): string; // overloaded signature 5 | function test(single: boolean): string; // overloaded signature 6 | function test(value: (string|number|boolean)): string { // implementation signature 7 | switch (typeof value) { 8 | case "string": 9 | return `My name is ${value}.`; 10 | case "number": 11 | return `I'm ${value} years old.`; 12 | case "boolean": 13 | return value ? "I'm single." : "I'm not single."; 14 | default: 15 | throw new Error("Invalid Operation!"); 16 | } 17 | } 18 | 19 | test("Remo"); // returns "My name is Remo." 20 | test(26); // returns "I'm 26 years old."; 21 | test(false); // returns "I'm not single."; 22 | test({ custom: "custom" }); // Error 23 | 24 | function test1(name: string): string; 25 | function test1(age: number): number; // Error 26 | function test1(single: boolean): string; 27 | function test1(value: (string|number|boolean)): string { 28 | switch (typeof value) { 29 | case "string": 30 | return `My name is ${value}.`; 31 | case "number": 32 | return `I'm ${value} years old.`; 33 | case "boolean": 34 | return value ? "I'm single." : "I'm not single."; 35 | default: 36 | throw new Error("Invalid Operation!"); 37 | } 38 | } 39 | 40 | } 41 | -------------------------------------------------------------------------------- /Chapter09/tslint.json: -------------------------------------------------------------------------------- 1 | { 2 | "extends": "tslint:all", 3 | "rules": { 4 | "array-type": [true, "array"], 5 | "ban-types": false, 6 | "comment-format": false, 7 | "completed-docs": false, 8 | "cyclomatic-complexity": false, 9 | "interface-name": false, 10 | "linebreak-style": false, 11 | "max-classes-per-file": false, 12 | "max-file-line-count": false, 13 | "max-line-length": [true, 140], 14 | "member-ordering": false, 15 | "newline-before-return": false, 16 | "no-any": false, 17 | "no-empty-interface": false, 18 | "no-floating-promises": false, 19 | "no-implicit-dependencies": [true, "dev"], 20 | "no-import-side-effect": false, 21 | "no-inferred-empty-object-type": false, 22 | "no-magic-numbers": false, 23 | "no-namespace": false, 24 | "no-null-keyword": false, 25 | "no-parameter-properties": false, 26 | "no-submodule-imports": false, 27 | "no-unbound-method": false, 28 | "no-unnecessary-class": false, 29 | "no-unnecessary-qualifier": false, 30 | "no-unsafe-any": false, 31 | "no-reference": false, 32 | "no-void-expression": false, 33 | "only-arrow-functions": false, 34 | "prefer-function-over-method": false, 35 | "prefer-template": false, 36 | "promise-function-async": false, 37 | "space-before-function-paren": false, 38 | "strict-boolean-expressions": false, 39 | "strict-type-predicates": false, 40 | "switch-default": false, 41 | "trailing-comma": false, 42 | "typedef": false, 43 | "variable-name": false, 44 | "newline-per-chained-call": false 45 | } 46 | } -------------------------------------------------------------------------------- /Chapter14/02_testing/tslint.json: -------------------------------------------------------------------------------- 1 | { 2 | "extends": "tslint:all", 3 | "rules": { 4 | "array-type": [true, "array"], 5 | "ban-types": false, 6 | "comment-format": false, 7 | "completed-docs": false, 8 | "cyclomatic-complexity": false, 9 | "interface-name": false, 10 | "linebreak-style": false, 11 | "max-classes-per-file": false, 12 | "max-file-line-count": false, 13 | "max-line-length": [true, 140], 14 | "member-ordering": false, 15 | "newline-before-return": false, 16 | "no-any": false, 17 | "no-empty-interface": false, 18 | "no-floating-promises": false, 19 | "no-implicit-dependencies": [true, "dev"], 20 | "no-import-side-effect": false, 21 | "no-inferred-empty-object-type": false, 22 | "no-magic-numbers": false, 23 | "no-namespace": false, 24 | "no-null-keyword": false, 25 | "no-parameter-properties": false, 26 | "no-submodule-imports": false, 27 | "no-unbound-method": false, 28 | "no-unnecessary-class": false, 29 | "no-unnecessary-qualifier": false, 30 | "no-unsafe-any": false, 31 | "no-reference": false, 32 | "no-void-expression": false, 33 | "only-arrow-functions": false, 34 | "prefer-function-over-method": false, 35 | "prefer-template": false, 36 | "promise-function-async": false, 37 | "space-before-function-paren": false, 38 | "strict-boolean-expressions": false, 39 | "strict-type-predicates": false, 40 | "switch-default": false, 41 | "trailing-comma": false, 42 | "typedef": false, 43 | "variable-name": false, 44 | "newline-per-chained-call": false 45 | } 46 | } -------------------------------------------------------------------------------- /Chapter11/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "chapter_14", 3 | "version": "1.0.0", 4 | "description": "", 5 | "main": "index.js", 6 | "scripts": { 7 | "start": "ts-node ./web/backend/index.ts", 8 | "build": "webpack", 9 | "lint": "tslint --project tsconfig.json -c tslint.json ./web/**/*.ts ./web/**/*.tsx" 10 | }, 11 | "keywords": [], 12 | "author": "Remo H. Jansen (http://www.remojansen.com)", 13 | "license": "MIT", 14 | "dependencies": { 15 | "body-parser": "1.18.2", 16 | "bootstrap": "4.0.0", 17 | "express": "4.16.2", 18 | "inversify": "4.11.1", 19 | "inversify-binding-decorators": "3.2.0", 20 | "inversify-express-utils": "5.2.1", 21 | "inversify-inject-decorators": "3.1.0", 22 | "mobx": "4.1.0", 23 | "mobx-react": "5.0.0", 24 | "pg": "7.4.1", 25 | "react": "16.2.0", 26 | "react-dom": "16.2.0", 27 | "react-router-dom": "4.2.2", 28 | "reflect-metadata": "0.1.12", 29 | "typeorm": "0.1.14" 30 | }, 31 | "devDependencies": { 32 | "@types/body-parser": "1.16.8", 33 | "@types/express": "4.11.1", 34 | "@types/node": "9.4.6", 35 | "@types/react": "16.0.40", 36 | "@types/react-dom": "16.0.4", 37 | "@types/react-router-dom": "4.2.5", 38 | "awesome-typescript-loader": "3.4.1", 39 | "copy-webpack-plugin": "4.5.1", 40 | "css-loader": "0.28.8", 41 | "extract-text-webpack-plugin": "3.0.2", 42 | "node-sass": "4.7.2", 43 | "resolve-url-loader": "2.2.1", 44 | "sass-loader": "6.0.6", 45 | "style-loader": "0.19.1", 46 | "ts-node": "5.0.1", 47 | "tslint": "5.9.1", 48 | "typescript": "2.8.1", 49 | "webpack": "3.10.0", 50 | "webpack-dev-server": "2.11.0" 51 | } 52 | } 53 | -------------------------------------------------------------------------------- /Chapter14/02_testing/test/calculator_component.test.tsx: -------------------------------------------------------------------------------- 1 | import { expect } from "chai"; 2 | import * as Enzyme from "enzyme"; 3 | import * as Adapter from "enzyme-adapter-react-16"; 4 | import * as React from "react"; 5 | import { stub } from "sinon"; 6 | import { Calculator } from "../src/frontend/calculator_component"; 7 | import { MathClient } from "../src/frontend/math_client"; 8 | 9 | // Here we will write some unit test for the claculator 10 | // component. 11 | 12 | Enzyme.configure({ adapter: new Adapter() }); 13 | 14 | describe("Calculator Component", () => { 15 | 16 | // Showcases how to use stub to isolate a component being 17 | // tested (Calculator) from its dependencies (MathClient) 18 | // also showcases how to test async code 19 | it("Should invoke client and set #result value when #submit.click is triggered", (done) => { 20 | 21 | const mathClient = new MathClient(); 22 | 23 | const mathClientStub = stub(mathClient, "pow"); 24 | mathClientStub.returns(Promise.resolve(8)); 25 | 26 | mathClientStub.callsFake((base: number, exponent: number) => { 27 | expect(base).to.equal(2); 28 | expect(exponent).to.equal(3); 29 | done(); 30 | }); 31 | 32 | const wrapper = Enzyme.mount(); 33 | 34 | expect(wrapper.find("input#base")).to.have.length(1); 35 | expect(wrapper.find("input#exponent")).to.have.length(1); 36 | expect(wrapper.find("button#submit_btn")).to.have.length(1); 37 | 38 | wrapper.find("input#base").simulate("change", { target: { value: "2" } }); 39 | wrapper.find("input#exponent").simulate("change", { target: { value: "3" } }); 40 | wrapper.find("button#submit_btn").simulate("click"); 41 | 42 | }); 43 | 44 | }); 45 | -------------------------------------------------------------------------------- /Chapter02/28_conditional_types.ts: -------------------------------------------------------------------------------- 1 | namespace conditional_types_demo { 2 | 3 | interface Animal { 4 | live(): void; 5 | } 6 | interface Dog extends Animal { 7 | woof(): void; 8 | } 9 | 10 | type Foo1 = Dog extends Animal ? number : string; // number 11 | type Bar1 = RegExp extends Dog ? number : string; // string 12 | 13 | type Flatten = T extends any[] ? T[number] : T; 14 | 15 | type arr1 = number[]; 16 | type flattenArr1 = Flatten; // number 17 | 18 | type arr2 = number[][]; 19 | type flattenArr2 = Flatten; // number[] 20 | 21 | type TypedFlatten = T extends Array ? U : T; 22 | 23 | // These are all now built into lib.d.ts! 24 | 25 | // Exclude from T those types that are assignable to U 26 | type Exclude = T extends U ? never : T; 27 | 28 | // Extract from T those types that are assignable to U 29 | type Extract = T extends U ? T : never; 30 | 31 | // string[] | number[] 32 | type Foo2 = Extract; 33 | 34 | // boolean 35 | type Bar2 = Exclude; 36 | 37 | // Exclude null and undefined from T 38 | type NonNullable = T extends null | undefined ? never : T; 39 | 40 | // Obtain the return type of a function type 41 | type ReturnType any> = T extends (...args: any[]) => infer R ? R : any; 42 | 43 | type func1 = () => number; 44 | type returnOfFunc1 = ReturnType; // number 45 | 46 | // Obtain the return type of a constructor function type 47 | type InstanceType any> = T extends new (...args: any[]) => infer R ? R : any; 48 | 49 | } 50 | -------------------------------------------------------------------------------- /Chapter12/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "chapter_15", 3 | "version": "1.0.0", 4 | "description": "", 5 | "main": "index.js", 6 | "scripts": { 7 | "start": "ts-node ./web/backend/index.ts", 8 | "build": "webpack", 9 | "lint": "tslint --project tsconfig.json -c tslint.json ./web/**/*.ts ./web/**/*.tsx" 10 | }, 11 | "keywords": [], 12 | "author": "Remo H. Jansen (http://www.remojansen.com)", 13 | "license": "MIT", 14 | "dependencies": { 15 | "@angular/common": "5.2.9", 16 | "@angular/compiler": "5.2.9", 17 | "@angular/core": "5.2.9", 18 | "@angular/forms": "5.2.9", 19 | "@angular/platform-browser": "5.2.9", 20 | "@angular/platform-browser-dynamic": "5.2.9", 21 | "@angular/router": "5.2.9", 22 | "body-parser": "1.18.2", 23 | "bootstrap": "4.0.0", 24 | "core-js": "2.5.4", 25 | "express": "4.16.2", 26 | "inversify": "4.11.1", 27 | "inversify-express-utils": "5.2.1", 28 | "pg": "7.4.1", 29 | "reflect-metadata": "0.1.12", 30 | "rxjs": "5.5.8", 31 | "typeorm": "0.1.14", 32 | "zone.js": "0.8.25" 33 | }, 34 | "devDependencies": { 35 | "@angular/language-service": "5.2.9", 36 | "@types/body-parser": "1.16.8", 37 | "@types/express": "4.11.1", 38 | "@types/node": "9.6.2", 39 | "awesome-typescript-loader": "3.4.1", 40 | "copy-webpack-plugin": "4.5.1", 41 | "css-loader": "0.28.8", 42 | "extract-text-webpack-plugin": "3.0.2", 43 | "node-sass": "4.7.2", 44 | "resolve-url-loader": "2.2.1", 45 | "sass-loader": "6.0.6", 46 | "style-loader": "0.19.1", 47 | "ts-node": "5.0.1", 48 | "tslint": "5.9.1", 49 | "typescript": "2.8.1", 50 | "webpack": "3.10.0", 51 | "webpack-dev-server": "2.11.0" 52 | } 53 | } 54 | -------------------------------------------------------------------------------- /Chapter12/web/frontend/services/movie_service.ts: -------------------------------------------------------------------------------- 1 | import { Injectable } from "@angular/core"; 2 | import { MovieInterface } from "../../universal/entities/movie"; 3 | import * as interfaces from "../interfaces"; 4 | 5 | @Injectable() 6 | export class MovieService implements interfaces.MovieService { 7 | 8 | public async getAll() { 9 | return new Promise(async (res, rej) => { 10 | try { 11 | const response = await fetch("/api/v1/movies/", { method: "GET" }); 12 | const movs: MovieInterface[] = await response.json(); 13 | // We use setTimeout to simulate a slow request 14 | // this should allow us to see the loading component 15 | setTimeout( 16 | () => { 17 | res(movs); 18 | }, 19 | 1500 20 | ); 21 | } catch (error) { 22 | rej(error); 23 | } 24 | }); 25 | } 26 | 27 | public async create(movie: Partial) { 28 | const response = await fetch( 29 | "/api/v1/movies/", 30 | { 31 | body: JSON.stringify(movie), 32 | headers: { 33 | "Accept": "application/json, text/plain, */*", 34 | "Content-Type": "application/json" 35 | }, 36 | method: "POST" 37 | } 38 | ); 39 | const newMovie: MovieInterface = await response.json(); 40 | return newMovie; 41 | } 42 | 43 | public async delete(id: number) { 44 | const response = await fetch(`/api/v1/movies/${id}`, { method: "DELETE" }); 45 | await response.json(); 46 | } 47 | 48 | } 49 | -------------------------------------------------------------------------------- /Chapter11/tslint.json: -------------------------------------------------------------------------------- 1 | { 2 | "extends": "tslint:all", 3 | "rules": { 4 | "array-type": [true, "array"], 5 | "ban-types": false, 6 | "comment-format": false, 7 | "completed-docs": false, 8 | "cyclomatic-complexity": false, 9 | "interface-name": false, 10 | "linebreak-style": false, 11 | "max-classes-per-file": false, 12 | "max-file-line-count": false, 13 | "max-line-length": [true, 140], 14 | "member-ordering": false, 15 | "newline-before-return": false, 16 | "no-any": false, 17 | "no-empty-interface": false, 18 | "no-floating-promises": false, 19 | "no-implicit-dependencies": [true, "dev"], 20 | "no-import-side-effect": false, 21 | "no-inferred-empty-object-type": false, 22 | "no-magic-numbers": false, 23 | "no-namespace": false, 24 | "no-null-keyword": false, 25 | "no-parameter-properties": false, 26 | "no-submodule-imports": false, 27 | "no-unbound-method": false, 28 | "no-unnecessary-class": false, 29 | "no-unnecessary-qualifier": false, 30 | "no-unsafe-any": false, 31 | "no-reference": false, 32 | "no-void-expression": false, 33 | "no-require-imports": false, 34 | "only-arrow-functions": false, 35 | "ordered-imports": false, 36 | "prefer-function-over-method": false, 37 | "prefer-template": false, 38 | "promise-function-async": false, 39 | "radix": false, 40 | "space-before-function-paren": false, 41 | "strict-boolean-expressions": false, 42 | "strict-type-predicates": false, 43 | "switch-default": false, 44 | "trailing-comma": false, 45 | "typedef": false, 46 | "variable-name": false, 47 | "newline-per-chained-call": false 48 | } 49 | } -------------------------------------------------------------------------------- /Chapter12/tslint.json: -------------------------------------------------------------------------------- 1 | { 2 | "extends": "tslint:all", 3 | "rules": { 4 | "array-type": [true, "array"], 5 | "ban-types": false, 6 | "comment-format": false, 7 | "completed-docs": false, 8 | "cyclomatic-complexity": false, 9 | "interface-name": false, 10 | "linebreak-style": false, 11 | "max-classes-per-file": false, 12 | "max-file-line-count": false, 13 | "max-line-length": [true, 140], 14 | "member-ordering": false, 15 | "newline-before-return": false, 16 | "no-any": false, 17 | "no-empty-interface": false, 18 | "no-floating-promises": false, 19 | "no-implicit-dependencies": [true, "dev"], 20 | "no-import-side-effect": false, 21 | "no-inferred-empty-object-type": false, 22 | "no-magic-numbers": false, 23 | "no-namespace": false, 24 | "no-null-keyword": false, 25 | "no-parameter-properties": false, 26 | "no-submodule-imports": false, 27 | "no-unbound-method": false, 28 | "no-unnecessary-class": false, 29 | "no-unnecessary-qualifier": false, 30 | "no-unsafe-any": false, 31 | "no-reference": false, 32 | "no-void-expression": false, 33 | "no-require-imports": false, 34 | "only-arrow-functions": false, 35 | "ordered-imports": false, 36 | "prefer-function-over-method": false, 37 | "prefer-template": false, 38 | "promise-function-async": false, 39 | "radix": false, 40 | "space-before-function-paren": false, 41 | "strict-boolean-expressions": false, 42 | "strict-type-predicates": false, 43 | "switch-default": false, 44 | "trailing-comma": false, 45 | "typedef": false, 46 | "variable-name": false, 47 | "newline-per-chained-call": false 48 | } 49 | } -------------------------------------------------------------------------------- /Chapter12/web/frontend/services/actor_service.ts: -------------------------------------------------------------------------------- 1 | import { Injectable } from "@angular/core"; 2 | import { ActorInterface } from "../../universal/entities/actor"; 3 | import * as interfaces from "../interfaces"; 4 | 5 | @Injectable() 6 | export class ActorService implements interfaces.ActorService { 7 | 8 | public async getAll() { 9 | return new Promise(async (res, rej) => { 10 | try { 11 | const response = await fetch("/api/v1/actors/", { method: "GET" }); 12 | const acts: ActorInterface[] = await response.json(); 13 | // We use setTimeout to simulate a slow request 14 | // this should allow us to see the loading component 15 | setTimeout( 16 | () => { 17 | res(acts); 18 | }, 19 | 1500 20 | ); 21 | } catch (error) { 22 | rej(error); 23 | } 24 | }); 25 | } 26 | 27 | public async create(actor: Partial) { 28 | const response = await fetch( 29 | "/api/v1/actors/", 30 | { 31 | body: JSON.stringify(actor), 32 | headers: { 33 | "Accept": "application/json, text/plain, *//*", // REMO // for / 34 | "Content-Type": "application/json" 35 | }, 36 | method: "POST" 37 | } 38 | ); 39 | const newActor: ActorInterface = await response.json(); 40 | return newActor; 41 | } 42 | 43 | public async delete(id: number) { 44 | const response = await fetch(`/api/v1/actors/${id}`, { method: "DELETE" }); 45 | await response.json(); 46 | } 47 | 48 | } 49 | -------------------------------------------------------------------------------- /Chapter15/06_language_services.ts: -------------------------------------------------------------------------------- 1 | import chalk from "chalk"; 2 | import { flatten, join } from "lodash"; 3 | import Ast, { DiagnosticMessageChain } from "ts-simple-ast"; 4 | import * as ts from "typescript"; 5 | 6 | function getAst(tsConfigPath: string, sourceFilesPath: string) { 7 | const ast = new Ast({ 8 | tsConfigFilePath: tsConfigPath, 9 | addFilesFromTsConfig: false 10 | }); 11 | ast.addExistingSourceFiles(sourceFilesPath); 12 | return ast; 13 | } 14 | 15 | const myAst = getAst("./tsconfig.json", "./app/*.ts"); 16 | const languageService = myAst.getLanguageService(); 17 | const files = myAst.getSourceFiles(); 18 | const interfaceDeclarations = flatten(files.map(f => f.getInterfaces())); 19 | 20 | const result = interfaceDeclarations.map(interfaceDeclaration => { 21 | 22 | const interfaceName = interfaceDeclaration.getName(); 23 | 24 | const implementations = languageService.getImplementations( 25 | interfaceDeclaration.getNameNode() 26 | ); 27 | 28 | const implementationNames = implementations.map(implementation => { 29 | const children = implementation.getNode().getChildren(); 30 | const identifier = children.filter( 31 | child => child.getKind() === ts.SyntaxKind.Identifier 32 | )[0]; 33 | const implementationName = identifier.getText(); 34 | return implementationName; 35 | }); 36 | 37 | return { 38 | interface: interfaceName, 39 | implementations: implementationNames 40 | }; 41 | 42 | }); 43 | 44 | console.log( 45 | result.forEach( 46 | o => console.log( 47 | `- ${o.interface} is implemented by ${join(o.implementations, ",")}` 48 | ) 49 | ) 50 | ); 51 | 52 | // - Weapon is implemented by Katana 53 | // - Named is implemented by Katana 54 | -------------------------------------------------------------------------------- /Chapter04/16_srp.ts: -------------------------------------------------------------------------------- 1 | namespace srp { 2 | 3 | class GodPerson { 4 | public name: string; 5 | public surname: string; 6 | public email: string; 7 | public constructor( 8 | name: string, surname: string, email: string 9 | ) { 10 | this.surname = surname; 11 | this.name = name; 12 | if (this.validateEmail(email)) { 13 | this.email = email; 14 | } else { 15 | throw new Error("Invalid email!"); 16 | } 17 | } 18 | public validateEmail(email: string) { 19 | const re = /\S+@\S+\.\S+/; 20 | return re.test(email); 21 | } 22 | public greet() { 23 | console.log( 24 | `Hi! I'm ${this.name}, 25 | you can reach me at ${this.email}` 26 | ); 27 | } 28 | } 29 | 30 | class Email { 31 | public static validateEmail(email: string) { 32 | const re = /\S+@\S+\.\S+/; 33 | return re.test(email); 34 | } 35 | } 36 | 37 | class Person { 38 | public name: string; 39 | public surname: string; 40 | public email: string; 41 | public constructor( 42 | name: string, surname: string, email: string 43 | ) { 44 | if (Email.validateEmail(email) === false) { 45 | throw new Error("Invalid email!"); 46 | } 47 | this.email = email; 48 | this.name = name; 49 | this.surname = surname; 50 | } 51 | public greet() { 52 | console.log( 53 | `Hi! I'm ${this.name}, 54 | you can reach me at ${this.email.toString()}` 55 | ); 56 | } 57 | } 58 | 59 | } 60 | -------------------------------------------------------------------------------- /Chapter15/03_ast_diagnostics.ts: -------------------------------------------------------------------------------- 1 | import chalk from "chalk"; 2 | import Ast, { DiagnosticMessageChain } from "ts-simple-ast"; 3 | 4 | /* 5 | This file demostrates how to use ts-simple-ast 6 | to find errors in TypeScript files 7 | */ 8 | 9 | function getAst(tsConfigPath: string, sourceFilesPath: string) { 10 | const ast = new Ast({ 11 | tsConfigFilePath: tsConfigPath, 12 | addFilesFromTsConfig: false 13 | }); 14 | ast.addExistingSourceFiles(sourceFilesPath); 15 | return ast; 16 | } 17 | 18 | function getErrors(ast: Ast) { 19 | 20 | const diagnostics = ast.getDiagnostics(); 21 | 22 | function dmcToString(dmc: DiagnosticMessageChain, msg: string = ""): string { 23 | const messageText = dmc.getMessageText(); 24 | const code = dmc.getCode(); 25 | msg += `${code} ${messageText}\n`; 26 | const next = dmc.getNext(); 27 | return next ? dmcToString(next, msg) : msg; 28 | } 29 | 30 | const errors = diagnostics.map(diagnostic => { 31 | const code = diagnostic.getCode(); 32 | const sourceOrUndefined = diagnostic.getSourceFile(); 33 | const source = sourceOrUndefined ? sourceOrUndefined.getFilePath() : ""; 34 | const line = sourceOrUndefined 35 | ? sourceOrUndefined.getLineNumberFromPos(diagnostic.getStart() || 0) 36 | : ""; 37 | const stringOrDMC = diagnostic.getMessageText(); 38 | const messageText = 39 | typeof stringOrDMC === "string" ? stringOrDMC : dmcToString(stringOrDMC); 40 | return ` 41 | ERROR CODE: ${code} 42 | DESCRIPTION: ${messageText} 43 | FILE: ${source} 44 | LINE: ${line} 45 | `; 46 | }); 47 | 48 | return errors; 49 | } 50 | 51 | const myAst = getAst("./tsconfig.json", "./app/broken.ts"); 52 | 53 | getErrors(myAst).forEach(err => console.log(chalk.red(err))); 54 | -------------------------------------------------------------------------------- /Chapter12/web/frontend/components/components.module.ts: -------------------------------------------------------------------------------- 1 | import { NgModule } from "@angular/core"; 2 | import { RouterModule } from "@angular/router"; 3 | import { CommonModule } from "@angular/common"; 4 | import { HeaderComponent } from "./header.component"; 5 | import { ContainerComponent, RowComponent, ColumnComponent } from "../components/grid.component"; 6 | import { CardComponent, CardImageComponent } from "../components/card.component"; 7 | import { ButtonComponent } from "../components/button.component"; 8 | import { ErrorComponent } from "./error.component"; 9 | import { LoadingComponent } from "./loading.component"; 10 | import { ListGroupItemComponent, ListGroupComponent } from "./listgroup.component"; 11 | import { ModalComponent } from "./modal.component"; 12 | import { TextFieldComponent } from "./textfield.component"; 13 | 14 | @NgModule({ 15 | declarations: [ 16 | HeaderComponent, 17 | ContainerComponent, 18 | RowComponent, 19 | ColumnComponent, 20 | CardComponent, 21 | CardImageComponent, 22 | ButtonComponent, 23 | ErrorComponent, 24 | LoadingComponent, 25 | ListGroupItemComponent, 26 | ListGroupComponent, 27 | ModalComponent, 28 | TextFieldComponent 29 | ], 30 | exports: [ 31 | HeaderComponent, 32 | ContainerComponent, 33 | RowComponent, 34 | ColumnComponent, 35 | CardComponent, 36 | CardImageComponent, 37 | ButtonComponent, 38 | ErrorComponent, 39 | LoadingComponent, 40 | ListGroupItemComponent, 41 | ListGroupComponent, 42 | ModalComponent, 43 | TextFieldComponent 44 | ], 45 | imports: [ 46 | RouterModule, 47 | CommonModule 48 | ] 49 | }) 50 | export class ComponentsModule { 51 | } 52 | -------------------------------------------------------------------------------- /Chapter02/29_polymorphic_this_type.ts: -------------------------------------------------------------------------------- 1 | namespace polymorphic_this_type_demo { 2 | 3 | interface Person { 4 | name?: string; 5 | surname?: string; 6 | age?: number; 7 | } 8 | 9 | class PersonBuilder { 10 | protected _details: T; 11 | public constructor() { 12 | this._details = {} as T; 13 | } 14 | public currentValue(): T { 15 | return this._details; 16 | } 17 | public withName(name: string): this { 18 | this._details.name = name; 19 | return this; 20 | } 21 | public withSurname(surname: string): this { 22 | this._details.surname = surname; 23 | return this; 24 | } 25 | public withAge(age: number): this { 26 | this._details.age = age; 27 | return this; 28 | } 29 | } 30 | 31 | let value1 = new PersonBuilder() 32 | .withName("name") 33 | .withSurname("surname") 34 | .withAge(28) 35 | .currentValue(); 36 | 37 | interface Employee extends Person { 38 | email: string; 39 | department: string; 40 | } 41 | 42 | class EmployeeBuilder extends PersonBuilder { 43 | public withEmail(email: string) { 44 | this._details.email = email; 45 | return this; 46 | } 47 | public withDepartment(department: string) { 48 | this._details.department = department; 49 | return this; 50 | } 51 | } 52 | 53 | let value2 = new EmployeeBuilder() 54 | .withName("name") 55 | .withSurname("surname") 56 | .withAge(28) 57 | .withEmail("name.surname@company.com") 58 | .withDepartment("engineering") 59 | .currentValue(); 60 | 61 | } 62 | -------------------------------------------------------------------------------- /Chapter12/web/frontend/components/card.component.ts: -------------------------------------------------------------------------------- 1 | import { Component, Input, Attribute } from "@angular/core"; 2 | 3 | @Component({ 4 | selector: "app-card-img", 5 | template: ` 6 | 7 | ` 8 | }) 9 | export class CardImageComponent { 10 | @Input() public imgPath!: string; 11 | @Input() public imgAlt!: string; 12 | } 13 | 14 | @Component({ 15 | selector: "app-card", 16 | template: ` 17 |
    18 |
    19 | 20 |
    21 |
    22 |
    {{title}}
    23 |

    24 | {{description}} 25 |

    26 | 27 | {{linkText}} 28 | 29 |
    30 |
    ` 31 | }) 32 | export class CardComponent { 33 | 34 | public title!: string; 35 | public description!: string; 36 | public linkPath!: string; 37 | public linkText!: string; 38 | public imgPath?: string; 39 | public imgAlt?: string; 40 | 41 | public constructor( 42 | @Attribute("title") title: string, 43 | @Attribute("description") description: string, 44 | @Attribute("linkPath") linkPath: string, 45 | @Attribute("linkText") linkText: string, 46 | @Attribute("imgPath") imgPath?: string, 47 | @Attribute("imgAlt") imgAlt?: string 48 | ) { 49 | this.title = title; 50 | this.description = description; 51 | this.linkPath = linkPath; 52 | this.linkText = linkText; 53 | this.imgPath = imgPath; 54 | this.imgAlt = imgAlt; 55 | } 56 | 57 | } 58 | -------------------------------------------------------------------------------- /Chapter12/web/frontend/components/modal.component.ts: -------------------------------------------------------------------------------- 1 | import { Component, Input, Output, EventEmitter } from "@angular/core"; 2 | 3 | @Component({ 4 | selector: "app-modal", 5 | template: ` 6 | 38 | ` 39 | }) 40 | export class ModalComponent { 41 | @Input() public title!: string; 42 | @Input() public acceptLabel!: string; 43 | @Input() public cancelLabel!: string; 44 | @Input() public error?: Error; 45 | @Output() public onCancel = new EventEmitter(); 46 | @Output() public onAccept = new EventEmitter(); 47 | } 48 | -------------------------------------------------------------------------------- /Chapter04/18_ocp.ts: -------------------------------------------------------------------------------- 1 | namespace ocp_demo { 2 | 3 | class Rectangle { 4 | public width!: number; 5 | public height!: number; 6 | } 7 | 8 | class AreaCalculator { 9 | public area(shapes: Rectangle[] ) { 10 | return shapes.reduce( 11 | (p, c) => { 12 | return p + (c.height * c.width); 13 | }, 14 | 0 15 | ); 16 | } 17 | } 18 | 19 | } 20 | 21 | namespace ocp_demo_2 { 22 | 23 | class Rectangle { 24 | public width!: number; 25 | public height!: number; 26 | } 27 | 28 | class Circle { 29 | public radius!: number; 30 | } 31 | 32 | class AreaCalculator { 33 | public area(shapes: Array) { 34 | return shapes.reduce( 35 | (p, c) => { 36 | if (c instanceof Rectangle) { 37 | return p + (c.width * c.height); 38 | } else { 39 | return p + (c.radius * c.radius * Math.PI); 40 | } 41 | }, 42 | 0 43 | ); 44 | } 45 | } 46 | 47 | } 48 | 49 | namespace ocp_demo_3 { 50 | 51 | abstract class Shape { 52 | public abstract area(): number; 53 | } 54 | 55 | class Rectangle extends Shape { 56 | public width!: number; 57 | public height!: number; 58 | public area() { 59 | return this.width * this.height; 60 | } 61 | } 62 | 63 | class Circle implements Shape { 64 | public radius!: number; 65 | public area() { 66 | return (this.radius * this.radius * Math.PI); 67 | } 68 | } 69 | 70 | class AreaCalculator { 71 | public area(shapes: Shape[]) { 72 | return shapes.reduce( 73 | (p, c) => p + c.area(), 74 | 0 75 | ); 76 | } 77 | } 78 | 79 | } 80 | -------------------------------------------------------------------------------- /Chapter08/06_reflect_metadata.ts: -------------------------------------------------------------------------------- 1 | import "reflect-metadata"; 2 | 3 | namespace reflect_metadata_demo1 { 4 | 5 | function logType(target: any, key: string) { 6 | const type = Reflect.getMetadata("design:type", target, key); 7 | console.log(`${key} type: ${type.name}`); 8 | } 9 | 10 | class Demo { 11 | @logType 12 | public attr1: string; 13 | public constructor(attr1: string) { 14 | this.attr1 = attr1; 15 | } 16 | } 17 | 18 | } 19 | 20 | namespace reflect_metadata_demo2 { 21 | 22 | function logParamTypes(target: any, key: string) { 23 | const types = Reflect.getMetadata( 24 | "design:paramtypes", 25 | target, 26 | key 27 | ); 28 | const s = types.map((a: any) => a.name).join(); 29 | console.log(`${key} param types: ${s}`); 30 | } 31 | 32 | class Foo {} 33 | interface FooInterface {} 34 | 35 | class Demo { 36 | @logParamTypes 37 | public doSomething( 38 | param1: string, 39 | param2: number, 40 | param3: Foo, 41 | param4: { test: string }, 42 | param5: FooInterface, 43 | param6: Function, 44 | param7: (a: number) => void 45 | ): number { 46 | return 1; 47 | } 48 | } 49 | 50 | // doSomething param types: String, Number, Foo, Object, Object, Function, Function 51 | 52 | } 53 | 54 | namespace reflect_metadata_demo3 { 55 | 56 | function logReturntype(target: any, key: string) { 57 | const returnType = Reflect.getMetadata( 58 | "design:returntype", 59 | target, 60 | key 61 | ); 62 | console.log(`${key} return type: ${returnType.name}`); 63 | } 64 | 65 | class Demo { 66 | @logReturntype 67 | public doSomething2(): string { 68 | return "test"; 69 | } 70 | } 71 | 72 | // doSomething2 return type: String 73 | 74 | } 75 | -------------------------------------------------------------------------------- /Chapter11/web/backend/controllers/movie_controller.ts: -------------------------------------------------------------------------------- 1 | import * as express from "express"; 2 | import { inject } from "inversify"; 3 | import { 4 | controller, 5 | httpGet, 6 | httpPost, 7 | requestParam, 8 | requestBody, 9 | response, 10 | httpDelete 11 | } from "inversify-express-utils"; 12 | import { Repository } from "typeorm"; 13 | import { TYPE } from "../constants/types"; 14 | import { Movie } from "../entities/movie"; 15 | 16 | @controller("/api/v1/movies") 17 | export class MovieController { 18 | 19 | private readonly _movieRepository: Repository; 20 | 21 | public constructor( 22 | @inject(TYPE.MovieRepository)movieRepository: Repository 23 | ) { 24 | this._movieRepository = movieRepository; 25 | } 26 | 27 | @httpGet("/") 28 | public async get( 29 | @response() res: express.Response 30 | ) { 31 | try { 32 | return await this._movieRepository.find(); 33 | } catch (e) { 34 | res.status(500); 35 | res.send(e.message); 36 | } 37 | } 38 | 39 | @httpPost("/") 40 | public async post( 41 | @response() res: express.Response, 42 | @requestBody() newMovie: Movie 43 | ) { 44 | if ( 45 | !(typeof newMovie.title === "string") || isNaN(newMovie.year) 46 | ) { 47 | res.status(400); 48 | res.send("Invalid Movie!"); 49 | } 50 | try { 51 | return await this._movieRepository.save(newMovie); 52 | } catch (e) { 53 | res.status(500); 54 | res.send(e.message); 55 | } 56 | } 57 | 58 | @httpDelete("/:id") 59 | public async delete( 60 | @requestParam("id") id: string, 61 | @response() res: express.Response 62 | ) { 63 | try { 64 | await this._movieRepository.deleteById(id); 65 | res.json({}); 66 | } catch (e) { 67 | res.status(500); 68 | res.send(e.message); 69 | } 70 | } 71 | 72 | } 73 | -------------------------------------------------------------------------------- /Chapter12/web/backend/controllers/movie_controller.ts: -------------------------------------------------------------------------------- 1 | import * as express from "express"; 2 | import { inject } from "inversify"; 3 | import { 4 | controller, 5 | httpGet, 6 | httpPost, 7 | requestParam, 8 | requestBody, 9 | response, 10 | httpDelete 11 | } from "inversify-express-utils"; 12 | import { Repository } from "typeorm"; 13 | import { TYPE } from "../constants/types"; 14 | import { Movie } from "../entities/movie"; 15 | 16 | @controller("/api/v1/movies") 17 | export class MovieController { 18 | 19 | private readonly _movieRepository: Repository; 20 | 21 | public constructor( 22 | @inject(TYPE.MovieRepository)movieRepository: Repository 23 | ) { 24 | this._movieRepository = movieRepository; 25 | } 26 | 27 | @httpGet("/") 28 | public async get( 29 | @response() res: express.Response 30 | ) { 31 | try { 32 | return await this._movieRepository.find(); 33 | } catch (e) { 34 | res.status(500); 35 | res.send(e.message); 36 | } 37 | } 38 | 39 | @httpPost("/") 40 | public async post( 41 | @response() res: express.Response, 42 | @requestBody() newMovie: Movie 43 | ) { 44 | if ( 45 | !(typeof newMovie.title === "string") || isNaN(newMovie.year) 46 | ) { 47 | res.status(400); 48 | res.send("Invalid Movie!"); 49 | } 50 | try { 51 | return await this._movieRepository.save(newMovie); 52 | } catch (e) { 53 | res.status(500); 54 | res.send(e.message); 55 | } 56 | } 57 | 58 | @httpDelete("/:id") 59 | public async delete( 60 | @requestParam("id") id: string, 61 | @response() res: express.Response 62 | ) { 63 | try { 64 | await this._movieRepository.deleteById(id); 65 | res.json({}); 66 | } catch (e) { 67 | res.status(500); 68 | res.send(e.message); 69 | } 70 | } 71 | 72 | } 73 | -------------------------------------------------------------------------------- /Chapter11/web/backend/controllers/actor_controller.ts: -------------------------------------------------------------------------------- 1 | import * as express from "express"; 2 | import { inject } from "inversify"; 3 | import { 4 | controller, 5 | httpGet, 6 | httpPost, 7 | requestBody, 8 | requestParam, 9 | response, 10 | httpDelete 11 | } from "inversify-express-utils"; 12 | import { Repository } from "typeorm"; 13 | import { TYPE } from "../constants/types"; 14 | import { Actor } from "../entities/actor"; 15 | 16 | @controller("/api/v1/actors") 17 | export class ActorController { 18 | 19 | private readonly _actorRepository: Repository; 20 | 21 | public constructor( 22 | @inject(TYPE.ActorRepository) actorRepository: Repository 23 | ) { 24 | this._actorRepository = actorRepository; 25 | } 26 | 27 | @httpGet("/") 28 | public async get( 29 | @response() res: express.Response 30 | ) { 31 | try { 32 | return await this._actorRepository.find(); 33 | } catch (e) { 34 | res.status(500); 35 | res.send(e.message); 36 | } 37 | } 38 | 39 | @httpPost("/") 40 | public async post( 41 | @response() res: express.Response, 42 | @requestBody() newActor: Actor 43 | ) { 44 | if ( 45 | !(typeof newActor.name === "string") || isNaN(newActor.yearBorn) 46 | ) { 47 | res.status(400); 48 | res.send("Invalid Actor!"); 49 | } 50 | try { 51 | return await this._actorRepository.save(newActor); 52 | } catch (e) { 53 | res.status(500); 54 | res.send(e.message); 55 | } 56 | } 57 | 58 | @httpDelete("/:id") 59 | public async delete( 60 | @requestParam("id") id: string, 61 | @response() res: express.Response 62 | ) { 63 | try { 64 | await this._actorRepository.deleteById(id); 65 | res.json({}); 66 | } catch (e) { 67 | res.status(500); 68 | res.send(e.message); 69 | } 70 | } 71 | 72 | } 73 | -------------------------------------------------------------------------------- /Chapter12/web/backend/controllers/actor_controller.ts: -------------------------------------------------------------------------------- 1 | import * as express from "express"; 2 | import { inject } from "inversify"; 3 | import { 4 | controller, 5 | httpGet, 6 | httpPost, 7 | requestBody, 8 | requestParam, 9 | response, 10 | httpDelete 11 | } from "inversify-express-utils"; 12 | import { Repository } from "typeorm"; 13 | import { TYPE } from "../constants/types"; 14 | import { Actor } from "../entities/actor"; 15 | 16 | @controller("/api/v1/actors") 17 | export class ActorController { 18 | 19 | private readonly _actorRepository: Repository; 20 | 21 | public constructor( 22 | @inject(TYPE.ActorRepository) actorRepository: Repository 23 | ) { 24 | this._actorRepository = actorRepository; 25 | } 26 | 27 | @httpGet("/") 28 | public async get( 29 | @response() res: express.Response 30 | ) { 31 | try { 32 | return await this._actorRepository.find(); 33 | } catch (e) { 34 | res.status(500); 35 | res.send(e.message); 36 | } 37 | } 38 | 39 | @httpPost("/") 40 | public async post( 41 | @response() res: express.Response, 42 | @requestBody() newActor: Actor 43 | ) { 44 | if ( 45 | !(typeof newActor.name === "string") || isNaN(newActor.yearBorn) 46 | ) { 47 | res.status(400); 48 | res.send("Invalid Actor!"); 49 | } 50 | try { 51 | return await this._actorRepository.save(newActor); 52 | } catch (e) { 53 | res.status(500); 54 | res.send(e.message); 55 | } 56 | } 57 | 58 | @httpDelete("/:id") 59 | public async delete( 60 | @requestParam("id") id: string, 61 | @response() res: express.Response 62 | ) { 63 | try { 64 | await this._actorRepository.deleteById(id); 65 | res.json({}); 66 | } catch (e) { 67 | res.status(500); 68 | res.send(e.message); 69 | } 70 | } 71 | 72 | } 73 | -------------------------------------------------------------------------------- /Chapter07/09_pointfree_style.ts: -------------------------------------------------------------------------------- 1 | namespace pointfree_style_demo_1 { 2 | 3 | interface Person { 4 | age: number; 5 | birthCountry: string; 6 | naturalizationDate: Date; 7 | } 8 | 9 | const OUR_COUNTRY = "Ireland"; 10 | 11 | const wasBornInCountry = (person: Person) => 12 | person.birthCountry === OUR_COUNTRY; 13 | 14 | const wasNaturalized = (person: Person) => 15 | Boolean(person.naturalizationDate); 16 | 17 | const isOver18 = (person: Person) => 18 | person.age >= 18; 19 | 20 | const isCitizen = (person: Person) => 21 | wasBornInCountry(person) || wasNaturalized(person); 22 | 23 | const isEligibleToVote = (person: Person) => 24 | isOver18(person) && isCitizen(person); 25 | 26 | isEligibleToVote({ 27 | age: 27, 28 | birthCountry: "Ireland", 29 | naturalizationDate: new Date(), 30 | }); 31 | 32 | } 33 | 34 | namespace pointfree_style_demo_2 { 35 | 36 | const either = ( 37 | funcA: (a: T1) => boolean, 38 | funcB: (a: T1) => boolean 39 | ) => (arg: T1) => funcA(arg) || funcB(arg); 40 | 41 | const both = ( 42 | funcA: (a: T1) => boolean, 43 | funcB: (a: T1) => boolean 44 | ) => (arg: T1) => funcA(arg) && funcB(arg); 45 | 46 | interface Person { 47 | age: number; 48 | birthCountry: string; 49 | naturalizationDate: Date; 50 | } 51 | 52 | const OUR_COUNTRY = "Ireland"; 53 | 54 | const wasBornInCountry = (person: Person) => 55 | person.birthCountry === OUR_COUNTRY; 56 | 57 | const wasNaturalized = (person: Person) => 58 | Boolean(person.naturalizationDate); 59 | 60 | const isOver18 = (person: Person) => 61 | person.age >= 18; 62 | 63 | // Pointfree style 64 | const isCitizen = either(wasBornInCountry, wasNaturalized); 65 | const isEligibleToVote = both(isOver18, isCitizen); 66 | 67 | isEligibleToVote({ 68 | age: 27, 69 | birthCountry: "Ireland", 70 | naturalizationDate: new Date(), 71 | }); 72 | 73 | } 74 | --------------------------------------------------------------------------------