Last active
May 3, 2024 04:07
-
-
Save AzrizHaziq/71f2316b9fb1e92816caa2da825dd10c to your computer and use it in GitHub Desktop.
JS & TS
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
<!-- default ngContent (https://github.com/angular/angular/issues/12530) --> | |
<div #ref><ng-content></ng-content></div> | |
<span *ngIf="ref.childNodes.length == 0"> | |
Display this if ng-content is empty! | |
</span> | |
<!-- ngTemplateOutletContext --> | |
// passing variables | |
<div *ngFor="let item of list; let i = index"> | |
<ng-container | |
[ngTemplateOutlet]="item.status === 'Open' ? openItem : notOpenItem" | |
[ngTemplateOutletContext]="{ $implicit: index item: item }"> | |
</ng-container> | |
</div> | |
<ng-template #openItem let-item="item" let-index> | |
<div>{{ item.status }}</div> | |
<p>This is open item</p> | |
</ng-template> | |
<ng-template #notOpenItem let-item="item"> | |
<div>{{ item.status }}</div> | |
<p>This is non-open Status Item</p> | |
</ng-template> |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
// https://blog.openreplay.com/the-ultimate-guide-to-browser-side-storage | |
(async () => { | |
// Storage API support | |
if (!navigator.storage) return; | |
const storage = await navigator.storage.estimate(); | |
console.log(`permitted: ${ storage.quota / 1024 } Kb`); | |
console.log(`used : ${ storage.usage / 1024 } Kb`); | |
console.log(`% used : ${ Math.round((storage.usage / storage.quota) * 100) }%`); | |
console.log(`remaining: ${ Math.floor((storage.quota - storage.usage) / 1024) } Kb`); | |
})(); | |
/////////////////////////////////////// | |
//// this | |
class Obj { | |
getThis = () => this | |
getThis2 () { | |
return this; | |
} | |
} | |
const obj2 = new Obj(); | |
obj2.getThis3 = obj2.getThis.bind(obj2); | |
obj2.getThis4 = obj2.getThis2.bind(obj2); | |
const answers = [ | |
1, obj2.getThis(), | |
2, obj2.getThis.call(a), | |
3, obj2.getThis2(), | |
4, obj2.getThis2.call(a), | |
5, obj2.getThis3(), | |
6, obj2.getThis3.call(a), | |
7, obj2.getThis4(), | |
8, obj2.getThis4.call(a), | |
]; | |
const a = { | |
a: 'a' | |
}; | |
const obj = { | |
getThis: () => this, | |
getThis2 () { | |
return this; | |
} | |
}; | |
obj.getThis3 = obj.getThis.bind(obj); | |
obj.getThis4 = obj.getThis2.bind(obj); | |
const answers = [ | |
1, obj.getThis(), | |
2, obj.getThis.call(a), | |
3, obj.getThis2(), | |
4, obj.getThis2.call(a), | |
5, obj.getThis3(), | |
6, obj.getThis3.call(a), | |
7, obj.getThis4(), | |
8, obj.getThis4.call(a), | |
]; | |
////////////// | |
function _0x39426c(e) { | |
function t(e) { | |
if ("string" == typeof e) | |
return function(e) {} | |
.constructor("while (true) {}").apply("counter"); | |
1 !== ("" + e / e).length || e % 20 == 0 ? function() { | |
return !0 | |
} | |
.constructor("debugger").call("action") : function() { | |
return !1 | |
} | |
.constructor("debugger").apply("stateObject"), | |
t(++e) | |
} | |
try { | |
if (e) | |
return t; | |
t(0) | |
} catch (e) {} | |
} | |
setInterval(function() { | |
_0x39426c() | |
}, 1000) | |
---------------- | |
// https://mariusschulz.com/blog/assertion-functions-in-typescript | |
function assertNonNullish<TValue>( | |
value: TValue, | |
message: string | |
): asserts value is NonNullable<TValue> { | |
if (value === null || value === undefined) { | |
throw Error(message); | |
} | |
} | |
const root = document.getElementById("root"); | |
root; // Type: HTMLElement | null | |
assertNonNullish(root, "Unable to find DOM element #root"); | |
root; // Type: HTMLElement | |
root.addEventListener("click", e => { | |
/* ... */ | |
}); |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
// https://gomakethings.com/formatting-dates-and-times-with-vanilla-js/ | |
// This will automatically use the user's browser language for formatting | |
let formatDate = now.toLocaleString(navigator.language, { | |
dateStyle: 'long', | |
timeStyle: 'short', | |
hour12: true | |
}); |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
/***** Log current active element in devtools *****/ | |
$('body').on('focusin', function() { | |
console.log(document.activeElement); | |
}); | |
/***** Pretty print state in html *****/ | |
<pre>{JSON.stringify(this.state, null, 2)}</pre> | |
/***** CSS Animation *****/ | |
* { animation-duration: 10s !important; } |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
// CURRY | |
const curry = ( | |
f, arr = [] | |
) => (...args) => ( | |
a => a.length === f.length ? | |
f(...a) : | |
curry(f, a) | |
)([...arr, ...args]); | |
// Curry function stolen from Professor Frisby's Mostly Adequate Guide | |
// curry :: ((a, b, ...) -> c) -> a -> b -> ... -> c | |
function curry(fn) { | |
const arity = fn.length; | |
return function $curry(...args) { | |
if (args.length < arity) { | |
return $curry.bind(null, ...args); | |
} | |
return fn.call(null, ...args); | |
}; | |
} | |
// By Erric Elliot | |
const curry = fn => (...args1) => { | |
if (args1.length === fn.length) { | |
return fn(...args1); | |
} | |
return (...args2) => { | |
const args = [...args1, ...args2]; | |
if (args.length >= fn.length) { | |
return fn(...args); | |
} | |
return curry(fn)(...args); | |
}; | |
}; | |
const trace = msg => x => (console.log(msg, x), x) | |
const trace = msg => x => (console.log(msg, x), x) | |
// COMPOSE | |
const compose = (...fns) => x => fns.reduceRight((y, f) => f(y), x); | |
// PIPE | |
const pipe = (...fns) => x => fns.reduce((y, f) => f(y), x); // or | |
const pipe = (...fns) => compose(...fns.reverse()) | |
// TRANSDUCER | |
const tArrayConcat = (a, c) => a.concat(c) // next reducer | |
const tTap = msg => step => (a, c) => { console.log(msg, a, c); return step(a, c) } // perform side effect | |
const tMap = f => step => (a, c) => step(a, f(c)); // transformer | |
const tFilter = predicate => step => (a, c) => predicate(c) ? step(a, c) : a; // filtering | |
const isEven = n => n % 2 === 0; | |
const double = n => n * 2; | |
const doubleEvens = compose( | |
tTap('start'), | |
tFilter(isEven), | |
tTap('after all even'), | |
tMap(double) | |
)(tArrayConcat); | |
console.log([1, 2, 3, 4, 5, 6].reduce(doubleEvens, [])) // [4, 8, 12] | |
// Pattern matching | |
// https://kyleshevlin.com/pattern-matching | |
function areWeThereYet(milesToGo) { | |
switch (true) { | |
case milesToGo <= 1: | |
return "We're hereeeee!" | |
case milesToGo <= 10: | |
return 'Almost. Just a little further.' | |
case milesToGo <= 100: | |
return 'A little more than an hour.' | |
case milesToGo <= 250: | |
return 'Just a half day away.' | |
case isEcommerceItems(items): | |
case isCMSItems(items): | |
return prepareForCollectionList(items) | |
case isDirector(person): | |
return 'Hello Director.' | |
case isRicky(person): | |
return 'Still here Ricky?' | |
case isStudent(person): | |
return `Hey, ${person.name}.` | |
default: | |
return "We're not even close so stop asking!" | |
} | |
} | |
function delay(timeout) { | |
return new Promise( | |
(resolve) => { | |
const timeoutHandle = | |
setTimeout(() => { | |
clearTimeout(timeoutHandle); | |
resolve() | |
}, timeout); | |
}); | |
} | |
// Promise pool | |
// https://betterprogramming.pub/5-javascript-promises-util-functions-to-spice-up-your-apps-665affca236c | |
function completePromisesInPool( | |
promiseFactories: () => Promise<any>, | |
maxPoolSize: number | |
) { | |
return new Promise((res) => { | |
let nextPromise = 0; | |
const runPromise = () => { | |
nextPromise++; | |
console.log({ nextPromise, promiseFactories}) | |
if (nextPromise > promiseFactories.length) { | |
res(); | |
return; | |
} | |
return promiseFactories[nextPromise - 1]() | |
.then(() => runPromise()) | |
} | |
Array.from({ length: maxPoolSize }) | |
.map(() => runPromise()); | |
}) | |
} | |
// promise sequence | |
function completeInSequence(promiseFactories: Promise[]) { | |
return promiseFactories.reduce( | |
(chain, promiseFactory) => chain.then(()=> promiseFactory()), | |
Promise.resolve() | |
); | |
} | |
// 2nd option for pool work | |
// https://stackoverflow.com/questions/40639432/what-is-the-best-way-to-limit-concurrency-when-using-es6s-promise-all/51020535#51020535 | |
const sleep = t => new Promise(rs => setTimeout(rs, t)) | |
async function doWork(iterator) { | |
for (let [index, item] of iterator) { | |
await sleep(1000) | |
console.log(index + ': ' + item) | |
} | |
} | |
const iterator = Array.from('abcdefghij').entries() | |
const workers = new Array(2).fill(iterator).map(doWork) | |
// ^--- starts two workers sharing the same iterator | |
Promise.allSettled(workers).then(() => console.log('done')) | |
------------------------------------ | |
Partial apply | |
const partialApply = (fn, ...fixedArgs) => { | |
return function (...remainingArgs) { | |
return fn.apply(this, fixedArgs.concat(remainingArgs)); | |
}; | |
}; | |
const add = (a, b) => a + b; | |
const add10 = partialApply(add, 10); | |
console.log(add10(5), add10(10)); | |
------------------------------------ | |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
// https://kentcdodds.com/blog/listify-a-java-script-array | |
// unfortunately TypeScript doesn't have Intl.ListFormat yet 😢 | |
// so we'll just add it ourselves: | |
type ListFormatOptions = { | |
type?: 'conjunction' | 'disjunction' | 'unit' | |
style?: 'long' | 'short' | 'narrow' | |
localeMatcher?: 'lookup' | 'best fit' | |
} | |
declare namespace Intl { | |
class ListFormat { | |
constructor(locale: string, options: ListFormatOptions) | |
public format: (items: Array<string>) => string | |
} | |
} | |
type ListifyOptions<ItemType> = { | |
type?: ListFormatOptions['type'] | |
style?: ListFormatOptions['style'] | |
stringify?: (item: ItemType) => string | |
} | |
function listify<ItemType>( | |
array: Array<ItemType>, | |
{ | |
type = 'conjunction', | |
style = 'long', | |
stringify = (thing: {toString(): string}) => thing.toString(), | |
}: ListifyOptions<ItemType> = {}, | |
) { | |
const stringified = array.map(item => stringify(item)) | |
const formatter = new Intl.ListFormat('en', {style, type}) | |
return formatter.format(stringified) | |
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
// Chainable | |
// https://blog.openreplay.com/forever-functional-chaining-calls-for-fluent-interfaces | |
const makeChainable = (obj) => | |
new Proxy(obj, { | |
get(target, property, receiver) { /* 1 */ | |
return typeof target[property] === "function" /* 2 */ | |
? (...args) => { /* 3 */ | |
const result = target[property](...args); /* 4 */ | |
return result === undefined ? receiver : result; /* 5 */ | |
} | |
: target[property]; /* 6 */ | |
} | |
}); | |
class Mobster { | |
constructor(firstName, lastName, nickname) { | |
this.firstName = firstName; | |
this.lastName = lastName; | |
this.nickname = nickname; | |
} | |
setFirstName(newFirst) { | |
this.firstName = newFirst; | |
} | |
setLastName(newLast) { | |
this.lastName = newLast; | |
} | |
setNickname(newNickname) { | |
this.nickname = newNickname; | |
} | |
getFullName() { | |
return `${this.firstName} "${this.nickname}" ${this.lastName}`; | |
} | |
} | |
const makeMobster = (...args) => makeChainable(new Mobster(...args)); | |
const gangster = makeMobster("Alphonse", "Capone", "Al"); | |
console.log(gangster.getFullName()); | |
// Alphonse "Al" Capone | |
console.log( | |
gangster | |
.setFirstName("Benjamin") | |
.setLastName("Siegel") | |
.setNickname("Bugsy") | |
.getFullName() | |
); | |
// Benjamin "Bugsy" Siegel |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
// https://www.chrisarmstrong.dev/posts/retry-timeout-and-cancel-with-fetch | |
const fetchWithRetries = async ( | |
url, | |
options, | |
retryCount = 0, | |
) => { | |
// split out the maxRetries option from the remaining | |
// options (with a default of 3 retries) | |
const { maxRetries = 3, ...remainingOptions } = options; | |
try { | |
return await fetch(url, remainingOptions); | |
} catch (error) { | |
// if the retryCount has not been exceeded, call again | |
if (retryCount < maxRetries) { | |
return fetchWithRetries(url, options, retryCount + 1); | |
} | |
// max retries exceeded | |
throw error; | |
} | |
} | |
//////////////////////// | |
// Create a promise that rejects after | |
// `timeout` milliseconds | |
const throwOnTimeout = (timeout) => | |
new Promise((_, reject) => | |
setTimeout(() => | |
reject(new Error("Timeout")), | |
timeout | |
), | |
); | |
const fetchWithTimeout = ( | |
url, | |
options = {}, | |
) => { | |
const { timeout, ...remainingOptions } = options; | |
// if the timeout option is specified, race the | |
// fetch call | |
if (timeout) { | |
return Promise.race([ | |
fetch(url, remainingOptions), | |
throwOnTimeout(timeout), | |
]); | |
} | |
return fetch(url, remainingOptions); | |
} | |
///////////////////////////////////? | |
const fetchWithCancel = (url, options = {}) => { | |
const controller = new AbortController(); | |
const call = fetch( | |
url, | |
{ ...options, signal: controller.signal }, | |
); | |
const cancel = () => controller.abort(); | |
return [call, cancel]; | |
}; | |
// We don't await this call, just capture the promise | |
const [promise, cancel] = fetchWithCancel( | |
'https://cataas.com/cat?json=true', | |
); | |
// await the promise to get the response | |
const response = await promise; | |
// ... | |
// cancel the request (e.g. if we have rendered | |
// something else) | |
cancel(); |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
// kentcdodds.com/blog/application-state-management-with-react | |
// context-provider-pattern.js | |
// src/count/count-context.js | |
import * as React from 'react' | |
const CountContext = React.createContext() | |
function useCount() { | |
const context = React.useContext(CountContext) | |
if (!context) { | |
throw new Error(`useCount must be used within a CountProvider`) | |
} | |
return context | |
} | |
function CountProvider(props) { | |
const [count, setCount] = React.useState(0) | |
const value = React.useMemo(() => [count, setCount], [count]) | |
return <CountContext.Provider value={value} {...props} /> | |
} | |
export {CountProvider, useCount} | |
// src/count/page.js | |
import * as React from 'react' | |
import {CountProvider, useCount} from './count-context' | |
function Counter() { | |
const [count, setCount] = useCount() | |
const increment = () => setCount(c => c + 1) | |
return <button onClick={increment}>{count}</button> | |
} | |
function CountDisplay() { | |
const [count] = useCount() | |
return <div>The current counter count is {count}</div> | |
} | |
function CountPage() { | |
return ( | |
<div> | |
<CountProvider> | |
<CountDisplay /> | |
<Counter /> | |
</CountProvider> | |
</div> | |
) | |
} | |
////////////////////////////////////////////////////////////////// | |
// context-reducer-pattern.ts | |
// https://kentcdodds.com/blog/how-to-use-react-context-effectively | |
// src/count-context.js | |
import * as React from 'react' | |
type Action = {type: 'increment'} | {type: 'decrement'} | |
type Dispatch = (action: Action) => void | |
type State = {count: number} | |
type CountProviderProps = {children: React.ReactNode} | |
const CountStateContext = React.createContext<State | undefined>(undefined) | |
const CountDispatchContext = React.createContext<Dispatch | undefined>( | |
undefined, | |
) | |
function countReducer(state: State, action: Action) { | |
switch (action.type) { | |
case 'increment': { | |
return {count: state.count + 1} | |
} | |
case 'decrement': { | |
return {count: state.count - 1} | |
} | |
default: { | |
throw new Error(`Unhandled action type: ${action.type}`) | |
} | |
} | |
} | |
function CountProvider({children}: CountProviderProps) { | |
const [state, dispatch] = React.useReducer(countReducer, {count: 0}) | |
return ( | |
<CountStateContext.Provider value={state}> | |
<CountDispatchContext.Provider value={dispatch}> | |
{children} | |
</CountDispatchContext.Provider> | |
</CountStateContext.Provider> | |
) | |
} | |
function useCountState() { | |
const context = React.useContext(CountStateContext) | |
if (context === undefined) { | |
throw new Error('useCountState must be used within a CountProvider') | |
} | |
return context | |
} | |
function useCountDispatch() { | |
const context = React.useContext(CountDispatchContext) | |
if (context === undefined) { | |
throw new Error('useCountDispatch must be used within a CountProvider') | |
} | |
return context | |
} | |
function useCount() { | |
return [useCountState(), useCountDispatch()] | |
} | |
export {CountProvider, useCountState, useCountDispatch, useCount} | |
///////////////////////// | |
// Event in React | |
// https://epicreact.dev/how-to-type-a-react-form-on-submit-handler/ | |
import * as React from 'react' | |
interface FormElements extends HTMLFormControlsCollection { | |
usernameInput: HTMLInputElement | |
} | |
interface UsernameFormElement extends HTMLFormElement { | |
readonly elements: FormElements | |
} | |
function UsernameForm({ onSubmitUsername }: { onSubmitUsername: (username: string) => void }) { | |
function handleSubmit(event: React.FormEvent<UsernameFormElement>) { | |
event.preventDefault() | |
onSubmitUsername(event.currentTarget.elements.usernameInput.value) | |
} | |
return ( | |
<form onSubmit={handleSubmit}> | |
<div> | |
<label htmlFor="usernameInput">Username:</label> | |
<input id="usernameInput" type="text" /> | |
</div> | |
<button type="submit">Submit</button> | |
</form> | |
) | |
} | |
///////////////////////////////////// | |
// kentcdodds.com/blog/application-state-management-with-react | |
function countReducer(state, action) { | |
switch (action.type) { | |
case 'INCREMENT': { | |
return {count: state.count + 1} | |
} | |
default: { | |
throw new Error(`Unsupported action type: ${action.type}`) | |
} | |
} | |
} | |
function CountProvider(props) { | |
const [state, dispatch] = React.useReducer(countReducer, {count: 0}) | |
const value = React.useMemo(() => [state, dispatch], [state]) | |
return <CountContext.Provider value={value} {...props} /> | |
} | |
function useCount() { | |
const context = React.useContext(CountContext) | |
if (!context) { | |
throw new Error(`useCount must be used within a CountProvider`) | |
} | |
const [state, dispatch] = context | |
const increment = () => dispatch({type: 'INCREMENT'}) | |
return { | |
state, | |
dispatch, | |
increment, | |
} | |
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
// https://betterprogramming.pub/rxjs-best-practices-7f559d811514 | |
import { TestScheduler } from "rxjs/testing"; | |
import { filter, map } from "rxjs/operators"; | |
describe("Awesome testing with Marble Diagrams", () => { | |
const scheduler = new TestScheduler((actual, expected) => { | |
expect(actual).toEqual(expected); | |
}); | |
it("should filter non-Water type pokemon and add attack property", () => { | |
scheduler.run(({ cold, expectObservable }) => { | |
const values = { | |
a: { name: "Bulbasur", type: "Grass" }, | |
b: { name: "Charmander", type: "Fire" }, | |
c: { name: "Squirtle", type: "Water" } | |
}; | |
const marbleDiagram = "-a-b-c|"; | |
const pokemon$ = cold(marbleDiagram, values); | |
const expectedMarbleDiagram = "-----c|"; | |
const expectedValues = { | |
c: { name: "Squirtle", type: "Water", attack: 30 } | |
}; | |
const result = pokemon$.pipe( | |
filter(({ type }) => type === "Water"), | |
map(pokemon => ({ ...pokemon, attack: 30 })) | |
); | |
expectObservable(result).toBe(expectedMarbleDiagram, expectedValues); | |
}); | |
}); | |
}); | |
--------------------------------------------- | |
import { TestScheduler } from "rxjs/testing"; | |
import { Observable } from "rxjs"; | |
import { filter } from "rxjs/operators"; | |
describe("Awesome testing with Marble Diagrams", () => { | |
const scheduler = new TestScheduler((actual, expected) => { | |
expect(actual).toEqual(expected); | |
}); | |
const isMultipleOfTen = (number: number) => number % 10 === 0; | |
it("should filter numbers that aren't multiples of ten", () => { | |
scheduler.run(({ cold, expectObservable }) => { | |
const values = { | |
a: 1, b: 2, c: 3, d: 4, e: 5, f: 6, g: 7, h: 8, i: 9, j: 10 | |
}; | |
const number$ = cold("-a-b-c-d-e-f-g-h-i-j|", values); | |
const expectedMarbleDiagram = "-------------------a|"; | |
const expectedValues = { a: 10 }; | |
const result = number$.pipe(filter(number => isMultipleOfTen(number))); | |
expectObservable(result).toBe(expectedMarbleDiagram, expectedValues); | |
}); | |
}); | |
}); |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
//////////////////////// EXTENDS /////////////////////// | |
interface Array<T> { | |
getBy<P extends keyof T>(prop: P, value: T[P]): T | null; | |
} | |
type Student = { | |
name: string; | |
age: number; | |
hasScar: boolean; | |
}; | |
type Teacher = { | |
name: string; | |
subject: string; | |
}; | |
Array.prototype.getBy = function <T, P extends keyof T>( | |
this: T[], | |
prop: P, | |
value: T[P] | |
): T | null { | |
return this.filter(item => item[prop] === value)[0] || null; | |
}; | |
const students: Student[] = [ | |
{ name: "Harry", age: 17, hasScar: true }, | |
{ name: "Ron", age: 17, hasScar: false }, | |
{ name: "Hermione", age: 16, hasScar: false } | |
]; | |
const teachers: Teacher[] = [ | |
{ name: "Snaip", subject: "Potions" }, | |
{ name: "McGonagall", subject: "Transfiguration" } | |
]; | |
const bestie = students.getBy("name", "Ron"); | |
const potionsTeacher = teachers.getBy("subject", "Potions") | |
type Student = { | |
name: string; | |
age: number; | |
hasScar: boolean; | |
}; | |
type Teacher = { | |
name: string; | |
subject: string | |
} | |
const students: Student[] = [ | |
{ name: "Harry", age: 17, hasScar: true }, | |
{ name: "Ron", age: 17, hasScar: false }, | |
{ name: "Hermione", age: 16, hasScar: false } | |
]; | |
const teachers: Teacher[] = [ | |
{ name: "Snaip", subject: "Potions" }, | |
{ name: "McGonagall", subject: "Transfiguration" } | |
] | |
function getBy<T, P extends keyof T>(model: T[], prop: P, value: T[P]): T | null { | |
return model.filter(item => item[prop] === value)[0] || null | |
} | |
const result = getBy(students, "naem", "Hermione") | |
// Error: Argument of type '"naem"' is not assignable to parameter of type '"name" | "age" | "hasScar"'. | |
const anotherResult = getBy(students, "hasScar", "true") | |
// Error: Argument of type '"true"' is not assignable to parameter of type 'boolean'. | |
const yetAnotherResult = getBy(students, "name", "Harry") | |
// That's cool, yetAnotherResult: Student | |
const lastResult = getBy(teachers, "subject", "Potions") | |
// Works for every type - lastResult: Teacher | |
//////////////////////// KEYOF //////////////////////////////////////// | |
interface Configuration { | |
baseUrl: string; | |
ttl: number; | |
} | |
type ConfigurationOption = keyof Configuration; | |
function set<T extends ConfigurationOption>(key: T, value: Configuration[T]) { | |
console.log(`Setting: ${key} to ${value}`); | |
} | |
set('baseUrl', 'https://moin.world'); | |
set('ttl', 42); | |
set('baseUrl', true); // Invalid! Compiler catches this. | |
type ConfigurationLastUpdated = { | |
[O in ConfigurationOption]: Date | |
}; | |
const updated: ConfigurationLastUpdated = { | |
baseUrl: new Date(), | |
ttl: new Date() | |
}; | |
type Color = 'red' | 'blue' | 'green'; | |
type ColorToRgb = { [C in Color]?: string }; | |
const rgbMap: ColorToRgb = { | |
red: '#ff0000' | |
}; | |
// Object is basically anything that is not undefined or null | |
const foo: Object = 'foo'; | |
/////////////////////// NAMESPACE /////////////////////////// | |
/***** Namespaces *******/ | |
namespace ApiModel { | |
export interface Order { | |
id: number; | |
total: number; | |
} | |
} | |
const abc: ApiModel.Order; = { id: 1, total: 20 } | |
////////////////////// PARTIAL ////////////////////////// | |
/***** Partial *******/ | |
type Partial<T> = { | |
[P in keyof T]?: T[P]; | |
}; | |
// using the interface but make all fields optional | |
import { Customer } from './api.model'; | |
export class MyComponent { | |
cust: Partial<Customer>; / | |
ngOninit() { | |
this.cust = { name: 'jane' }; // no error throw because all fields are optional | |
} | |
} | |
/////////////////////// REDUX //////////////////////////////////// | |
const initialState: LoginState = { | |
username: '', | |
password: '', | |
isLoading: false, | |
error: '', | |
isLoggedIn: false, | |
variant: 'login', | |
}; | |
interface LoginState { | |
username: string; | |
password: string; | |
isLoading: boolean; | |
error: string; | |
isLoggedIn: boolean; | |
variant: 'login' | 'forgetPassword'; | |
} | |
type LoginAction = | |
| { type: 'abc', password: string, payload: { isLoading: boolean } } | |
| { type: 'login' | 'success' | 'error' | 'logOut' } | |
| { type: 'field'; fieldName: string; payload: boolean }; | |
function loginReducer(state: LoginState, action: LoginAction): LoginState { | |
switch (action.type) { | |
case 'abc': { | |
return { | |
...state, | |
isLoading: action.payload.isLoading, | |
password : action.password | |
} | |
} | |
case 'field': { | |
return { | |
...state, | |
[action.fieldName]: action.payload, | |
}; | |
} | |
case 'login': { | |
return { | |
...state, | |
error: '', | |
isLoading: true, | |
}; | |
} | |
case 'success': { | |
return { | |
...state, | |
isLoggedIn: true, | |
isLoading: false, | |
}; | |
} | |
case 'error': { | |
return { | |
...state, | |
error: 'Incorrect username or password!', | |
isLoggedIn: false, | |
isLoading: false, | |
username: '', | |
password: '', | |
}; | |
} | |
case 'logOut': { | |
return { | |
...state, | |
isLoggedIn: false, | |
}; | |
} | |
default: | |
return state; | |
} | |
} | |
/////////////// Array ////////////////// | |
type Car = 'BMW' | 'MERC' | 'TOYOTA'; | |
type Moto = 'KAWASAKI' | 'EX5' | 'HONDA'; | |
const arr: Array<Car | Moto> = ['HONDA', 'BMW', 'MERC']; | |
const arr2: Car[] | Moto[] = ['KAWASAKI', 'EX5', 'HONDA']; | |
//////////////// TUPLE /////////////////////// | |
interface IQuery<TReturn, UParams extends any[] = []> { | |
(...args: UParams): Promise<TReturn> | |
} | |
type Params: [title: string, artist: string] | |
const findSongAlbum: IQuery<Album, Params> = (title, artist) => { | |
// data fetching code... | |
const albumName = '1989'; | |
return Promise.resolve({ | |
title: albumName | |
}); | |
} | |
/////////////////// Conditional TS ////////////////////////////? | |
// https://icanmakethiswork.blogspot.com/2021/04/the-service-now-api-and-typescript_83.html | |
export type PropertyValue< | |
TAllTrueFalse extends DisplayValue, | |
TValue = string, | |
TDisplayValue = string | |
> = TAllTrueFalse extends 'all' | |
? ValueAndDisplayValue<TValue, TDisplayValue> | |
: TAllTrueFalse extends 'true' | |
? TDisplayValue | |
: TValue; | |
const reasonAll: PropertyValue<'all', string, string | null> = { | |
"display_value": null, | |
"value": "" | |
}; | |
const reasonTrue: PropertyValue<'true', string, string | null> = null; | |
const reasonFalse: PropertyValue<'false', string, string | null> = ''; | |
////////////////////////////////////////// | |
// https://kentcdodds.com/blog/typescript-function-syntaxes?ck_subscriber_id=539300779 | |
////////////////////////////////////////// | |
// Simple type for a function, use => | |
type FnType = (arg: ArgType) => ReturnType | |
// Every other time, use : | |
type FnAsObjType = { | |
(arg: ArgType): ReturnType | |
} | |
interface InterfaceWithFn { | |
fn(arg: ArgType): ReturnType | |
} | |
const fnImplementation = (arg: ArgType): ReturnType => { | |
/* implementation */ | |
} | |
////////////////////////////////////////////// | |
declare function MathFn(a: number, b: number): number | |
declare namespace MathFn { | |
let operator: '+' | |
} | |
const sum: typeof MathFn = (a, b) => a + b | |
sum.operator = '+' | |
/////////////////////////////////////////// | |
interface MathUtilsInterface { | |
sum(a: number, b: number): number | |
} | |
class MathUtils implements MathUtilsInterface { | |
sum(a: number, b: number): number { | |
return a + b | |
} | |
} | |
const math = new MathUtils() | |
const sum = math.sum | |
sum(1, 2) | |
/////////////////////////////////// | |
declare const sum: { | |
(a: number, b: number): number | |
operator: string | |
} | |
export default sum | |
const sum = (a: number, b: number): number => a + b | |
sum.operator = '+' | |
////////////////////////////////// | |
type asyncSumCb = (result: number) => void | |
// define all valid function signatures | |
function asyncSum(a: number, b: number): Promise<number> | |
function asyncSum(a: number, b: number, cb: asyncSumCb): void | |
// define the actual implementation | |
// notice cb is optional | |
// also notice that the return type is inferred, but it could be specified | |
// as `void | Promise<number>` | |
function asyncSum(a: number, b: number, cb?: asyncSumCb) { | |
const result = a + b | |
if (cb) return cb(result) | |
else return Promise.resolve(result) | |
} | |
/////////////////////////////////? | |
function* generator(start: number) { | |
const newStart: number = yield start + 1 | |
yield newStart + 2 | |
} | |
var iterator = generator(0) | |
console.log(iterator.next()) // { value: 1, done: false } | |
console.log(iterator.next(3)) // { value: 5, done: false } | |
console.log(iterator.next()) | |
/////////////////////////////////// | |
// React | |
function arrayify2<Type>(a: Type): Array<Type> { | |
return [a] | |
} | |
// use extends unknown because < is JSX or generic? | |
const arrayify = <Type extends unknown>(a: Type): Array<Type> => [a] | |
//////////////////////////////////// | |
// Tuples and types with interfaces | |
interface FixedLengthArray<T, U, L extends number> extends Array<T| U> { | |
0: T | |
1: U | |
length: L | |
} | |
type Foo = FixedLengthArray<number, boolean, 6> | |
const foo1: Foo = [1, true, 3, 4, 5, false]; | |
/////////////////////////////////////// | |
// https://www.totaltypescript.com/concepts/the-prettify-helper | |
type Prettify<T> = { | |
[K in keyof T]: T[K]; | |
} & {}; | |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment