Un objecte és un un conjunt de valors que estan relacionats i que es gestionen com un conjunt.

Objecte

Pots crear un objecte directament tal com es mostra a continuació:

const mike: any = {}

Les propietats d'un objecte de tipus any són dinàmiques i en qualsevol moment poden canviar

En aquest cas l'objecte està buit.

Pots verificar que l'objecte no té una variable name:

const mike = {}
console.log(mike.name) // undefined

Si vols, pots afegir una variable name a l'objecte:

const mike: any = {}
mike.name = "Mike Smith"
console.log(mike) // { name: "Mike Smith", }

La sintaxis és la mateixa que utilitzes per definir una variable, excepte que dius que la variable name està definida dins el context de la variable mike.

Les variables d'un objecte és coneixen amb el nom de propietats de l'objecte, encara que a efectes pràctics no deixen se ser variables.

També pots definir les propietats de l'objecte al crear l'objecte tal com es mostra a continuació:

const mike: any = { 
    name: "Mike Smith", 
    email: "mike@gmail.com" 
} 

La diferència principal és que enlloc d'utilitzar = fas servir :.

Pots "esborrar" el contingut d'una propietat d'un objecte utilitzant null:

const mike: any = {
    name: "Mike Smith",
    email: "mike@gmail.com"
}

mike.email = null

console.log(mike.age)     // undefined
console.log(mike.email)  // null

En qualsevol moment pots consultar les propietats d'un objecte amb la funció Object.keys():

const mike: any = { 
  name: "Mike Smith", 
  email: "mike@gmail.com",
}

mike.age = 45

console.log(Object.keys(mike))  // [ 'name', 'email', 'age']

Pots eliminar una propietat de l'objecte amb delete:

const mike: any = {
    name: "Mike Smith",
    email: "mike@gmail.com"
}

delete mike.email

console.log(Object.keys(mike)) // [ "name" ]

En qualsevol moment pots utilitzar la funció Object.values() per conèixer els valors de les propietats, o Object.entries() si necessites a la vegada les claus de les propietats i el seu valor.

const mike: any = {
    name: "Mike Smith",
    email: "mike@gmail.com"
}
mike.phone = "+1 898 898 99"

console.log(Object.entries(mike))

El resultat és una llista de llistes:

[
  [ "name", "Mike Smith" ], [ "email", "mike@gmail.com" ], [ "phone", "+1 898 898 99" ]
]

Enlloc d'utilitzar la notació ., pots utilitzar la notació [] per treballar amb les propietats d'un objecte:

const mike: any = {
    ["name"]:  "Mike Smith",
}
mike["phone"] = "+1 898 898 99"

console.log(mike["name"]) // Mike Smith

Això és util quan utilitzes propietats dinàmiques, o noms que no respecten la sintaxis admissible de TypeScript pels noms:

const cart: any = {}
while (true) {
    const product = prompt("Product?")
    if (product == null) break
    cart[product] = cart[product] == undefined ? 1 : cart[product] += 1
}

console.log(cart)
> bun test.ts
Product? Pizza
Product? Pizza
Product? 
{
  Pizza: 2,
}

O quan vols recorrer totes les propietats d'un objecte és mitjançant un bucle for..in:

const mike: any = {
    name: "Mike Smith",
    email: "mike@gmail.com"
}
mike.phone = "+1 898 898 99"

for(const key in mike){
    console.log(`${key}:\t${mike[key]}`)
}

A continuació tens el resultat:

> bun test.ts
name:   Mike Smith
email:  mike@gmail.com
phone:  +1 898 898 99

Activitat

1.- Escriu el codi, una linia per cada acció:

  1. Crea un objecte user buit.
  2. Afegeix la propietat name amb el valor John.
  3. Afegeix la propietat surname amb el valor Smith
  4. Modifica el valor de name a Pete.
  5. Elimina la propietat surname de l'objecte.

let user: any = {}
user.name = "John"
user.surname = "Smith"
user.name = "Pete"
delete user.surname

2.- Tenim un objecte que emmagatzema el salaris del nostre equip:

const salaries:any = {
    John: 100,
    Ann: 160,
    Pete: 130
}

Escriu el codi per sumar tots els salaris i emmagatzemar el resultat a la variable result. En aquest exemple el resultat ha de ser 390.

Si salaries estigues buit el resultat hauria de ser 0.

const salaries:any = {
    John: 100,
    Ann: 160,
    Pete: 130
}

let result = 0
for (const key in salaries) {
    result += salaries[key]
}

console.log(result)

Tipus

Un tipus et permet definir les propietats que ha de tenir un objecte si o si.

Per exemple, pots definir el tipus Person:

type Person = {
    name: string,
    email: string
}

Al crear una variable, pots dir a Typescript que aquella variable només pot tenir objectes de tipus Person:

const laura: Person = {
    name: "Laura",
    email: "laura@gmail.com"
}

Ara TypeScript t'avisa de que hi ha coses que pots fer que no hauries de fer:

laura.age = 34             // La propietat 'age' no existeix en el tipus Person.
delete laura.name          // No hauries d'eliminar la propietat 'age' d'un objecte de tipus Person

console.log(laura.age)

TypesScript només avisa: pots executar el codi sense problemes:

> bun test.ts
{
  email: "laura@gmail.com",
  age: 34,
}

I en aquest cas no passa res.

Però en l'exemple que es mostra a continuació si que es produeix un error:

function print(person: Person) {
    console.log(person.name.toUpperCase())
}

delete laura.name // Error
print(laura)

Llavors el codi no funciona com tenia que funcionar perquè la funció print espera un paràmetre de tipus Person que tingui la propietat name:

> bun test.ts
...
12 |function print(p: Person) {
13 |     console.log(p.name.toUpperCase())
                       ^
TypeError: undefined is not an object (evaluating 'p.name.toUpperCase')
      at print (C:\Users\david\Workspace\yup\test.ts:13:19)
      at C:\Users\david\Workspace\yup\test.ts:17:1

Duck typing 🦆

Un tipus només serveix per dir quines propietats ha de tenir un objecte, el nom del tipus és indiferent.

Un objecte és d'un tipus concret si té tots les propietats que demana aquell tipus.

Per exemple, en aquest codi els tipus Person i Alien són intercanviables:

type Alien = {
    name: string,
    email: string
}

function print(a: Alien) {
    console.log(a.name)
}

const laura: Person = {
    name: "Laura",
    email: "laura@gmail.com"
}

print(laura)
const alienLaura: Alien = laura

En aquest cas un Alien 👽 i un Person són iguals 👱: tenen, i només tenen, name i email.

Pots escriure un codi per enviar missatges a la Laura: tens el seu correu, i no té cap importància si és un alien o no.

Propietats opcionals

Pots utilitzar el símbol ? per definir una propietat com opcional.

En aquest exemple la propietat email és de tipus string | undefined

type Person = {
    name: string,
    email?: string
}

const laura: Person = {name: "Laura"}

laura.email.toUpperCase()

Si intentes utilitzar la propietat email directament, TypeScript et diu que potser no està definida.

Primer has de verificar que està definida:

const laura: Person = {name: "Laura"}

if (laura.email != undefined){
    laura.email.toUpperCase()
}

Subtipus

Al crear un objecte, el tipus de la variable ha de coincidir amb les propietats de l'objecte.

Aquest codi és erroni perquè el tipus Named no té la propietat age:

type Person = {
    name: string,
    age?: number
}

type Named = {
    name: string
}

const named: Named = {name: "Laura", age: 30}

Però pots passar la referència de la variable a qualsevol altre variable que tingui un tipus que sigui subtipus del tipus.

O de manera més llana, que tingui algunes (i només algunes) de les propietats del tipus.

type Person = {
    name: string,
    age?: number
}

type Named = {
    name: string
}

function print(named: Named) {
    console.log(named.name)
}

const laura: Person = {name: "Laura", age: 30}

const _: Named = laura
print(laura)

Qualsevol objecte de tipus Person es pots utilitzar com un objecte de tipus Named.

Llistes

També pots restringir el tipus d'elements que pot tenir una llista:

const team: Person[] = [{
    name: "Laura",
    email: "laura@gmail.com"
}, {
    name: "David",
    email: "david@gmail.com"
}]

D'aquesta manera pots escriure codi que estas segur que funcionarà:

console.log(team.map(p => p.name)) // [ "Laura", "David" ]

Constructor

Si has de crear molts cops un objecte amb les mateixes propietats pots utilitzar una funció.

function Employee(name: string, salary: number): Employee {
    return {name, salary}
}

const david = Employee("David", 10000)

console.log(david.salary) // 10000

Dins de la funció pots aplicar la lògica que vulguis 😁:

function Employee(name: string, salary: number): Employee {
    if (name == "David")
        salary += 30000
    return {name, salary}
}

const david = Employee("David", 10000)

console.log(david.salary) // 40000

Tipus anònim

En TypeScript el nom del tipus no te gaire importància: pots declarar el tipus que ha de tenir una variable o el paràmetre d'una funció sense necessitat de posar-li nom:

const eva: { name: string, email: string } = {
    name: "Eva",
    email: "eva@gmail.com"
}

function sendMessage(to: { name: string, email: string }, message: string) {
    console.log(`${to.email} `)
}

sendMessage(eva, "Ens veiem dissabte")

També és útil quan una funció vol tornar un objecte com resultat:

function sendMessage(to: { name: string, email: string }, message: string): { error: boolean, errorMessage?: string } {
    console.log(`${to.email} `)
    return {error: true, errorMessage: "Encara no està implementat"}
}

const result = sendMessage({name: "Eva", email: "eva@gmail.com"}, "Ens veiem dissabte")
if (result.error) {
    console.log(result.errorMessage)
}

Funció

Una funció és un valor com qualsevol altre i pots ser la propietat d'un objecte:

const chat = {
    name: "whale",
    posts: [""],
    post: function (message: string) {
        this.posts.push(message)
        console.log(message)
    }
}

chat.post("Hello, World!")
chat.post("What do you think about ...")

console.log(chat.posts)

Una funció definida com a propietat t'un objecte pot accedir a les altres propietats de l'objecte amb l'expressió this.

Naturalment també pots definir el tipus de l'objecte:

type Chat = {
    name: string,
    posts: string[]
    post: (message: string) => void
}

const chat: Chat = {
    name: "whale",
    posts: [],
    post: function (message: string) {
        this.posts.push(message)
        console.log(message)
    }
}