Michał Michalczuk
I am fullstack software developer #dotnet #angular #typescript, IT trainer. Both fascinated and terrified by technology advancement.
Michał Michalczuk / Bartosz Cytrowski
github repository link
@cytrowski
@michalczukm
JavaScript that scales
TypeScript
ES6
ES5
TS works for you only if the whole project is in TS
babel or tsc -
it no difference
class Task {
private _title = '';
constructor(title: string) {
this._title = title;
}
get title() {
return this._title;
}
set title(value) {
const formattedTitle = value.trim();
if (formattedTitle === '') {
throw TypeError('Title cannot be empty');
}
this._title = formattedTitle;
}
}
task.ts
var Task = /** @class */ (function () {
function Task(title) {
this._title = '';
this._title = title;
}
Object.defineProperty(Task.prototype, "title", {
get: function () {
return this._title;
},
set: function (value) {
var formattedTitle = value.trim();
if (formattedTitle === '') {
throw TypeError('Title cannot be empty');
}
this._title = formattedTitle;
},
enumerable: true,
configurable: true
});
return Task;
}());
"use strict";
function _instanceof(left, right) {
if (
right != null &&
typeof Symbol !== "undefined" &&
right[Symbol.hasInstance]
) {
return right[Symbol.hasInstance](left);
} else {
return left instanceof right;
}
}
function _classCallCheck(instance, Constructor) {
if (!_instanceof(instance, Constructor)) {
throw new TypeError("Cannot call a class as a function");
}
}
function _defineProperties(target, props) {
for (var i = 0; i < props.length; i++) {
var descriptor = props[i];
descriptor.enumerable = descriptor.enumerable || false;
descriptor.configurable = true;
if ("value" in descriptor) descriptor.writable = true;
Object.defineProperty(target, descriptor.key, descriptor);
}
}
function _createClass(Constructor, protoProps, staticProps) {
if (protoProps) _defineProperties(Constructor.prototype, protoProps);
if (staticProps) _defineProperties(Constructor, staticProps);
return Constructor;
}
function _defineProperty(obj, key, value) {
if (key in obj) {
Object.defineProperty(obj, key, {
value: value,
enumerable: true,
configurable: true,
writable: true
});
} else {
obj[key] = value;
}
return obj;
}
var Task =
/*#__PURE__*/
(function() {
function Task(title) {
_classCallCheck(this, Task);
_defineProperty(this, "_title", void 0);
this._title = title;
}
_createClass(Task, [
{
key: "title",
get: function get() {
return this._title;
},
set: function set(value) {
var formattedTitle = value.trim();
if (formattedTitle === "") {
throw TypeError("Title cannot be empty");
}
this._title = formattedTitle;
}
}
]);
return Task;
})();
My IDE don't know TS
TS guards your types only at compilation time
npm install runtypes
the classic one
the twisted classic
the all in
BFF
~/api/products/1
{
"id": 1,
"name": "Brown bag",
"availability": true
}
{
"_ID": 1,
"name": "Brown bag",
"isAvailable": true
}
type Product = {
id: number;
name: string;
availability: boolean;
}
type Product = {
_ID: number;
name: string;
isAvailable: boolean;
}
{
"id": 1,
"name": "Brown bag",
"availability": true
}
~/api/products/1
{
"id": 1,
"name": "Brown bag",
"availability": true
}
{
"_ID": 1,
"name": "Brown bag",
"isAvailable": true
}
type Product = {
id: number;
name: string;
availability: boolean;
}
{
"id": 1,
"name": "Brown bag",
"availability": true
}
{
"_ID": 1,
"name": "Brown bag",
"isAvailable": true
}
type Product = {
_ID: number;
name: string;
isAvailable: boolean;
}
import * as React from 'react';
import { Component } from 'react';
import { Todo } from '@trails/typings';
import './app.css';
interface State {
todos: Todo[]
}
export class App extends Component<{}, State> {
// ...
front-end (React)
import {
Controller, Get, Post, Body
} from '@nestjs/common';
import { Todo, Product } from '@trails/typings';
import { TodosService } from './todos.service';
@Controller()
export class AppController {
constructor(
private readonly todosService: TodosService
) { }
@Get('todos')
async getTodo(): Promise<Todo[]> {
return this.todosService.get();
}
// ...
back-end (NestJS)
export interface Todo {
id: number;
title: string;
}
type
{
"compilerOptions": {
"paths": {
"@trails/typings": [
"libs/typings/src/index.ts"
]
}
}
}
tsconfig.json
Presentation/Delivery
Logic
Data Access
class Product {
public int Id { get; set; }
public string Name { get; set; }
}
class ProductModel {
public int Id { get; set; }
public string Name { get; set; }
}
class ProductApiModel {
public int Id { get; set; }
public string Name { get; set; }
}
ProductModel Map(Product product) {
return new ProductModel {
Id = product.Id,
Name = product.Name
}
}
Presentation/Delivery
Logic
Data Access
type Product = {
id: number;
name: string;
}
type ProductModel = Product;
type ProductApiModel = ProductModel;
type ProductApiModel = ProductModel & {
isDraft: boolean;
canSave: boolean;
};
// result type
{
id: number;
name: string;
isDraft: boolean;
canSave: boolean;
}
type ProductApiModel =
Omit<ProductModel, 'name'>
// result type
{
id: number;
}
type ProductApiModel = Partial<ProductModel>;
// result
{
id?: number;
name?: string;
}
type ProductApiModel = Readonly<ProductModel>;
// result
{
readonly id: number;
readonly name: string;
}
Dummy components
Container components
Data Access: API/Store
Presentation/Delivery
Logic
Data Access
Dummy components
Container components
Data Access: API/Store
Presentation/Delivery
Logic
Data Access
type Payment = {
id: number;
vendorId: number;
clientId: number;
currency?: string;
amount: number;
}
type NotWhatYouExpected = {
id?: number;
vendorId: number;
payerId: number;
currencyISO: string;
value: string;
}
export interface Payment {
id: number;
vendorId: number;
clientId: number;
currency?: string;
amount: number;
}
async get(): Promise<Payment[]> {
return this.httpService
.get<Payment[]>(`${this.externalServiceUrl}/payments`)
.pipe(
map(response => response.data)
)
.toPromise();
}
payments.service.ts
payments.model.ts
[
{
id: 5,
vendorId: 100,
payerId: 200,
currencyISO: 'PLN',
value: '1000'
},
{
vendorId: 101,
payerId: 201,
currencyISO: 'EUR',
value: '1001'
}
]
real live response
[
{
id: 5,
vendorId: 100,
clientId: 200,
currency: 'PLN',
amount: 1000
},
{
id: 6,
vendorId: 101,
clientId: 201,
currency: 'EUR',
amount: 1001
}
]
if (isDuck(pet)) {
pet.quack();
} else {
throw new Error(`OMG it's not a duck!`);
}
/// ---------
function isDuck(pet: Duck | Fish): pet is Duck {
return (<Duck>pet).quack !== undefined;
}
npm install runtypes
import {
Record, Number, String,
Undefined, Static
} from 'runtypes';
export const PaymentRecord = Record({
id: Number,
vendorId: Number,
clientId: Number,
currency: String.Or(Undefined),
amount: Number
});
export type Payment = Static<typeof PaymentRecord>;
payments.model.ts
export type Payment = Static<typeof PaymentRecord>;
// result type
{
id: number,
vendorId: number,
clientId: number,
currency?: string,
amount: number
}
payments.model.ts
async get(): Promise<Payment[]> {
return this.httpService
.get<Payment[]>(`${this.externalServiceUrl}/payments`)
.pipe(
map(response => response.data),
tap(payment => PaymentRecord.check(payment))
)
.toPromise();
}
payments.service.ts
[Nest] 36664 - 5/4/2019, 5:06:41 PM
[ExceptionsHandler] Expected number,
but was undefined +2061065ms
ValidationError: Expected number,
but was undefined
+ some extra advices
By Michał Michalczuk
Repo: https://github.com/infoshareacademy/infoshare-2019-typescript-between-layers
I am fullstack software developer #dotnet #angular #typescript, IT trainer. Both fascinated and terrified by technology advancement.