# Working with types - Natalia
## 1.4 How to check if a string is part of an Enum?
### Using `in` operator to check if the key string is part of an Enum
```typescript
enum BreakingBadCharacters {
WalterWhite = "Heisenberg",
JessePinkman = "Cap'n Cook",
SaulGoodman = "Slippin' Jimmy",
}
const isCharacterInBreakingBad = (character: string): boolean => {
return character in BreakingBadCharacters;
};
console.log(isCharacterInBreakingBad("WalterWhite")); // true
console.log(isCharacterInBreakingBad("GusFring")); // false
```
### Using `Object.values()` to check if the value string is part of an Enum
```typescript
enum BreakingBadNicknames {
Heisenberg = "Walter White",
CapnCook = "Jesse Pinkman",
SlippinJimmy = "Saul Goodman",
}
const isNicknameFromBreakingBad = (nickname: string): boolean => {
// let nicknames = Object.values(BreakingBadNicknames);
let nicknames: string[] = Object.values(BreakingBadNicknames) as string[];
return nicknames.includes(nickname);
};
console.log(isNicknameFromBreakingBad("Walter White")); // true
console.log(isNicknameFromBreakingBad("Mike")); // false
```
### Using `Object.values()` to check if both the key or value string is part of an Enum
```typescript
enum BreakingBadElements {
Methamphetamine = "Blue Crystal",
HydrofluoricAcid = "Dissolve",
Chromium = "Cr",
}
const isElementOrCompoundInBreakingBad = (input: string): boolean => {
const keys = Object.keys(BreakingBadElements) as string[];
const values = Object.values(BreakingBadElements) as string[];
return keys.includes(input) || values.includes(input);
};
console.log(isElementOrCompoundInBreakingBad("Methamphetamine")); // true (key)
console.log(isElementOrCompoundInBreakingBad("Blue Crystal")); // true (value)
console.log(isElementOrCompoundInBreakingBad("Pizza")); // false
```
## 1.5. How to make only some properties of an interface mandatory?
https://plainenglish.io/blog/how-to-make-certain-properties-optional-in-typescript-9b4f8e85c5de
I feel like the way this blog develop the problem is really good. So I'll replicate those step here.
```typescript
interface SpellCaster {
name: string;
surname: string;
house: string;
age: number; //Normal way to make a field mandatory
pet?: Animal; //Normal way to make a field optional is using `?`
}
interface Animal {
name: string;
age: number;
}
//Assume we want to create a function to return the fullname
const spellCasterFullName = ({ surname, name }: SpellCaster): string => {
return `${name} ${surname}`;
};
const spellCaster1: SpellCaster = {
name: "Harry",
surname: "Potter",
house: "Gryffindor",
age: 17,
};
console.log(spellCasterFullName(spellCaster1.surname, spellCaster1.name));
```
this works well, but now when you want to call the function spellCasterFullName else where, you would need to create a new SpellCaster
`console.log(spellCasterFullName({ surname: "Granger", name: "Herminone" }));`
this `{surname:"Granger", name: "Herminone"}` doesn't match SpellCaster type
Then, it's where Partial<T> come to play:
```typescript
function spellCasterFullName({ name, surname }: Partial<SpellCaster>): string {
return `${name} ${surname}`;
}
console.log(spellCasterFullName({ surname: "Granger", name: "Herminone" }));
```
But then, `{surname:string, name: string}` become `{surname:string|undefined, name: string|undefined}` so we need to perfom some type check like
we want name and surname mandatory and match with the type that define in the interface SpellCaster
=> we can use `Pick<T, Keys>` which selects some properties Keys from the T.
```typescript
const spellCasterFullName = ({
name,
surname,
}: Pick<SpellCaster, "name" | "surname">): string => {
return `${name} ${surname}`;
};
console.log(spellCasterFullName(spellCaster1));
console.log(spellCasterFullName({ surname: "Granger", name: "Herminone" }));
```
### A step further, in the case that we want to return characteristic of that person based on their house. it can be achieved by
```typescript
const getCharacteristic = (house: string): string => {
return "brave"; //let assume that all house characteristic return bravery, but we know that this value might differ from houses
};
const spellCasterFullName = ({ name, surname, house }: SpellCaster): string => {
if (house) {
const characteristic = getCharacteristic(house);
return `${name} ${surname} is ${characteristic}`;
}
return `${name} ${surname}`;
};
console.log(spellCasterFullName(spellCaster1));
```
but we don't need all SpellCaster properties just to return short name where there is no house
then here is a solution
```typescript
const spellCasterFullName = (
sp: Partial<SpellCaster> & Pick<SpellCaster, "name" | "surname">,
): string => {
const { name, surname, house } = sp;
if (house) {
const characteristic = getCharacteristic(house);
return `${name} ${surname} is ${characteristic}`;
}
return `${name} ${surname}`;
};
console.log(spellCasterFullName(spellCaster1));
console.log(spellCasterFullName({ surname: "Granger", name: "Herminone" }));
```
## 1.6 How to assign properties to an object dynamically?
```typescript
//wrong way:
const character = {}
character.name = "Severus Snape";
//or
const character = {
name = "Severus Snape";
}
character.age = 20
```
### 1. Declaring the Object with Explicit Type
This method is used when the shape of your object is known and unlikely to change
```typescript
const character: { name: string } = {};
character.name = "Severus Snape";
console.log("After dynamic assignment: ", character.name);
```
### 2. Using the Index Signature of the Object
Flexible object that can have any number of properties of a certain type
```typescript
const magicalCreature: { [name: string]: any } = {};
// Dynamically adding more properties
magicalCreature.species = "Snowy Owl";
magicalCreature.owner = "Harry Potter";
console.log(`Updated creature: ${magicalCreature.name}, a ${magicalCreature.species}, owned by ${magicalCreature.owner}`);
//not even need to define the name property
```
### 3. Using the Record Utility Type
```typescript
const houseColors: Record<string, string[]> = {
Gryffindor: ["Scarlet", "Gold"],
};
console.log("Before dynamic assignment: ", houseColors.Gryffindor);
// Dynamically adding more houses and their colors
houseColors.Hufflepuff = ["Yellow", "Black"];
houseColors.Ravenclaw = ["Blue", "Bronze"];
houseColors.Slytherin = ["Green", "Silver"];
console.log("After dynamic assignment: ");
for (const house in houseColors) {
console.log(`${house}: ${houseColors[house].join(" and ")}`);
}
```
## 1.7 How to exclude properties readonly from a type
Original Character Type
```typescript
type Wizard = {
readonly id: number;
readonly name: string;
house: string;
};
```
### 1. Making All Properties Mutable
```typescript
type RemoveReadonlyForAll<T> = {
-readonly [P in keyof T]: T[P];
};
type EditableWizard = RemoveReadonlyForAll<Wizard>;
const editableWizard: EditableWizard = {
id: 1,
name: "Harry Potter",
house: "Gryffindor"
};
// Demonstrating mutability
editableWizard.id = 2; // Success
editableWizard.name = "Harry James Potter"; // Success
editableWizard.house = "Slytherin"; // Success
```
### 2. Removing readonly from Specific Properties
```typescript
type RemoveReadonly<T, K extends keyof T> = {
-readonly [P in K]: T[P];
} & {
[P in Exclude<keyof T, K>]: T[P];
};
// Making only the 'name' property mutable
type NameChangeableWizard = RemoveReadonly<Wizard, 'name'>;
const nameChangeableWizard: NameChangeableWizard = {
id: 3,
name: "Hermione Granger",
house: "Gryffindor"
};
// Trying to mutate
// nameChangeableWizard.id = 4; // Error: Cannot assign to 'id' because it is a read-only property.
nameChangeableWizard.name = "Luna Lovegood"; // Success
// nameChangeableWizard.house = "Ravenclaw"; // This would still work as 'house' was mutable from the start.
```
### 3. Manually Adjusting Properties
```typescript
// Exclude 'id' and 'name' from Wizard, then add them back as mutable
type IdentityChangedWizard = Omit<Wizard, 'id' | 'name'> & {
id: number;
name: string;
};
const identityChangedWizard: IdentityChangedWizard = {
id: 5,
name: "Ron Weasley",
house: "Gryffindor"
};
// Demonstrating allowed changes
identityChangedWizard.id = 6; // Success
identityChangedWizard.name = "Ronald Bilius Weasley"; // Success
// identityChangedWizard.house = "Hufflepuff"; // This would still work as 'house' was mutable from the start.
```
## 1.8 How to extract a type from a interface property?
```typescript
interface MagicalBook {
title: string;
author: string;
magicalProperties: any;
}
interface HogwartsCurriculum {
potions: {
textbook: MagicalBook,
supplementary: MagicalBook[]
};
defenseAgainstTheDarkArts: {
textbook: MagicalBook,
supplementary: MagicalBook[]
};
// Additional classes can be added here
}
```
### Extract the specific structure using indexed access type
```typescript
// Extracting the type for supplymentary book of potion class
type PotionClassSupplymentaryBook = HogwartsCurriculum["potions"]["supplymentary"]
//PotionClassSupplymentaryBook = MagicalBook[]
// Extracting the type for Potions class reading materials
type PotionsReadingMaterials = HogwartsCurriculum["potions"];
// PotionsReadingMaterials = {
// textbook: MagicalBook,
// supplementary: MagicalBook[]
// }
```
Question: why using `type PotionClassSupplymentaryBook = HogwartsCurriculum["potions"]["supplymentary"]
//PotionClassSupplymentaryBoo` but not `type PotionClassSupplymentaryBook = MagicalBook[]`?
In a scenario where the *structure of MagicalBook changes, or the potions class structure is updated to include a new category of books*. If you had used `MagicalBook[]` directly, these changes would not be reflected in the `PotionClassSupplementaryBook` type without manual intervention. However, by using `HogwartsCurriculum["potions"]["supplementary"]`, your type definitions remain accurate and **up-to-date** with minimal effort.
### Utilizing keyof to Enumerate Hogwarts Classes
```typescript
type HogwartsClass = keyof HogwartsCurriculum;
//HogwartsClass = "potions" | "defenseAgainstTheDarkArts"
```
- Example Usage
We can use this HogwartsClass type to ensure that functions dealing with classes only accept valid class names.
```typescript
function listMaterialsForClass(className: HogwartsClass) {
// Implementation: list all materials for the given class
}
```
## 1.9 How to restrict a number to a certain range
```typescript
type Enumerate<N extends number, Acc extends number[] = []> = Acc['length'] extends N
? Acc[number]
: Enumerate<N, [...Acc, Acc['length']]>;
type Range<F extends number, T extends number> = Exclude<Enumerate<T>, Enumerate<F>>;
type T = Range<3,5>;
```
### Goal
We want to create a type that represents a sequence of numbers from the range 3-5 . This sequence is a range of numbers from 3 (inclusive) to 5 (exclusive).
### Enumerate Type
First, we have a helper type called Enumerate. This generates a sequence of numbers starting from 0 up to (but not including) a number N you specify.
```typescript
type Enumerate<N extends number, Acc extends number[] = []> = Acc['length'] extends N
? Acc[number]
: Enumerate<N, [...Acc, Acc['length']]>
//Enumerate<3>
//=> This would generate a union from 0 to 2: 0|1|2
```
Here's how it works:
- It takes a number N and starts with an empty list (or array) called Acc (short for "Accumulator").
- It looks at the length of `Acc`. If the length matches N, it stops and gives us a list of numbers from 0 up to N-1.
- If the length of Acc is not yet N, it adds the current length of Acc to the end of Acc and tries again. This step is repeated until Acc contains N numbers.
#### Dry Run Example: Enumerate<3>
Let's see how this works step by step to generate a sequence [0, 1, 2].
```typescript
type Enumerate<N extends number, Acc extends number[] = []> = Acc['length'] extends N
? Acc[number]
: Enumerate<N, [...Acc, Acc['length']]>
//Initial Call:
Enumerate<3>
//Step 1: Start with an empty Acc
//Enumerate<3>
Acc = []
Acc.length = 0 //(Does not match N=3)
//=> Acc['length'] extends 3 return false
//=> call Enumerate<N, [...Acc, Acc['length']]>
// that now is
Enumerate<3, [0]>
//Step 2: Add Acc.length to Acc and recurse
//Enumerate<3, [0]>
New Acc = [0]
Acc.length = 1 //(Does not match N=3)
//Recursive Call
Enumerate<3, [0,1]>
//Step 3: Add Acc.length to Acc and recurse
//Enumerate<3, [0,1]>
New Acc = [0,1]
Acc.length = 2 //(Does not match N=3)
//Recursive Call Enumerate<3, [0,1,2]>
//Step 4: Add Acc.length to Acc and recurse
//Enumerate<3, [0,1,2]>
New Acc = [0,1,2]
Acc.length = 3 //(match N=3)
//=> Acc['length'] extends 3 return true
//so we stop here and return Acc["number"]
```
Final Result
The process stops because Acc.length now matches N.
The resulting type from `Acc[number]` is `0 | 1 | 2`, representing each number in the sequence as a union of literals.
### Range Type
```typescript
type Range<F extends number, T extends number> = Exclude<Enumerate<T>, Enumerate<F>>;
```
How It Works:
- `Exclude<Enumerate<T>, Enumerate<F>>`: This uses TypeScript's `Exclude` utility type to subtract the numbers from `0` to `F-1` (generated by `Enumerate<F>`) from the numbers `0` to `T-1` (generated by `Enumerate<T>`). The result is a type that represents each number between `F` and `T-1` as a literal.
Example:
```typescript
type T = Range<3, 5>;
```
this equal to
```typescript
Exclude<Enumerate<5>, Enumerate<3>>
```
`Enumerate<5>` generates a union type `0 | 1 | 2 | 3 | 4`.
`Enumerate<3>` generates a union type `0 | 1 | 2`.
=> `Exclude< 0|1|2|3|4 , 0|1|2> `
=> result in new union type: `3 | 4`
=> `type T = 3 | 4`
### Conclusion
```typescript
type Enumerate<N extends number, Acc extends number[] = []> = Acc['length'] extends N
? Acc[number]
: Enumerate<N, [...Acc, Acc['length']]>;
type Range<F extends number, T extends number> = Exclude<Enumerate<T>, Enumerate<F>>;
type T = Range<3,5>;
//=> type T = 3 | 4
```
## 1.10 How to Check if an Object Implements a Type or Interface?
**By using "User Defined Type Guard"**
Imagine we're at Hogwarts, and we need to verify if a particular magical device, let's call it a `SpellChecker`, implements the `ISpell` interface. This interface ensures that every magical device can cast a spell (castSpell) and has a spell name (spellName).
First, we define the ISpell interface
```typescript
interface ISpell {
spellName: string;
castSpell: () => void;
}
```
To check if a magical device is truly capable of casting spells as per the ISpell interface, we conjure a **"User Defined Type Guard"** function.
```typescript
const implementsISpell = (object: any): object is ISpell =>{
return object &&
typeof object.spellName === 'string' &&
typeof object.castSpell === 'function';
}
```
We now have a magical device that claims to be capable of checking spells:
```typescript
const spellChecker = {
spellName: "Lumos",
castSpell: () => console.log("Let there be light"),
};
//Implement type checking at run time
if (implementsISpell(spellChecker)) {
console.log("spellChecker implements ISpell");
} else {
console.log("spellChecker does not implement ISpell");
}
```
### Pros and Cons
**Pros**
- Type Safety: This method ensures that objects adhere to specific structures at runtime, offering a layer of type safety beyond compile-time checks.
- Flexibility: Type guards can be customized to check for various properties and methods, providing flexibility in validation logic.
**Cons**
- Manual Checks: For objects with many properties, writing and updating type guards can become cumbersome.
- Runtime Overhead: These checks occur at runtime, potentially impacting performance if used excessively.
### Use Cases
- Input Validation: Ensuring that dynamic content, such as user input or API responses, matches expected types and structures.
- Interoperability: Verifying that objects from external libraries or modules meet the required interfaces for integration.
- Debugging: Aiding in debugging by catching type-related errors at runtime, providing an additional layer of error checking.
## 1.11 Keeping the relation key-value using Objects.entries
Define a WizardInventory type
```typescript
type WizardInventory = {
wand: number,
potionName: string
};
```
The `KeyValuePairs` mapped type transforms an object type into an array of `[key, value]` tuples, keeping the association between each key and its corresponding value type.
```typescript
type KeyValuePairs<T> = {
[P in keyof T]: [P, T[P]];
}[keyof T][];
```
**Applying KeyValuePairs to WizardInventory**
```typescript
KeyValuePairs<WizardInventory>
```
When we apply `KeyValuePairs<T>` to `WizardInventory`, here's how TypeScript processes the type:
**1. Iterating Over Keys with `[P in keyof T]`**
```typescript
keyof WizardInventory //result in type "wand" | "potionName"
//the mapped type [P in keyof T] iterates over each of these keys
[P in keyof T] //loop over "wand" and "potitionName"
```
**2. Creating Tuples with [P, T[P]]**
On each iteration, TypeScript creates a tuple [P, T[P]]:
- For `P = "wand"`, `T[P]` is number.
- The tuple becomes `["wand", number]`.
- For `P = "potionName"`, `T[P]` is string.
- The tuple becomes `["potionName", string]`.
- At this point, we have a mapped type where each key from WizardInventory is mapped to a tuple of the key name and its type:
```typescript
{
wand: ["wand", number],
potionName: ["potionName", string]
}
```
**3. Converting to a Union of Tuples with [keyof T][]**
Applying [keyof T] on this mapped type, which extracts the types of each property into a union. Result in:
```typescript
["wand", number] | ["potionName", string]
```
Finally, adding [] at the end constructs an array type from this union:
```typescript
(["wand", number] | ["potionName", string])[]
```
Final result:
```typescript
type WizardInventory = {
wand: number,
potionName: string
};
type KeyValuePairs<T> = {
[P in keyof T]: [P, T[P]];
}[keyof T][];
type InventoryEntries = KeyValuePairs<WizardInventory>;
// Type is (["wand", number] | ["potionName", string])[]
```
other example:
```typescript
type KeyValuePairs<T> = {
[P in keyof T]: [P, T[P]];
}[keyof T][];
//Example 1:
type EventHandlers = {
onClick: () => void;
onMouseEnter: () => void;
onKeyPress: (key: string) => boolean;
};
type HandlerEntries = KeyValuePairs<EventHandlers>;
//type is ["onClick", () => void] | ["onMouseEnter", () => void] | ["onKeyPress", (key: string) => boolean])[]
type MixedTypesObject = {
id: number;
isValid: boolean;
name: string;
computeScore: (values: number[]) => number;
};
type MixedEntries = KeyValuePairs<MixedTypesObject>;
// Type is [("id", number) | ("isValid", boolean) | ("name", string) | ("computeScore", (values: number[]) => number)][]
```