Ga naar hoofdinhoud

Les 3 - Vue3 Services

Les overzicht

  • De algemene structuur van Vue3 beter begrijpen en onderzoeken wat de architectuur is en hoe een Vue applicatie opgebouwd wordt.

Samenvatting

  • Vue folder structuur en architectuur
    • Deel 2 van de TOH applicatie bouwen

Stap 1 - data in de hero.service.ts

Op dit moment zitten we zowel in HeroesView als in HeroDetailsView met dezelfde lijst van heroes. Als ik op 1 plaats deze lijst zou veranderen, dan moet ik ervoor zorgen dat de lijst op alle andere plaatsen waar hij gebruikt wordt ook geupdate wordt. Dit is niet handig. Intro services.

  • Services zijn een manier om data te centraliseren
  • Herinner het singleton design pattern waar we het in les 1 over gehad hebben
  • Data die altijd hetzelfde moet zijn doorheen de applicatie
  • In Vue wordt een Service ook wel een composable genoemd en met de composition API kan je een composable maken die ook als een singleton werkt

Step by step

  • Maak een folder services
  • Maak in de folder services een file hero.service.ts
  • inhoud:
const useHeroes = () => {
return {}
}

export { useHeroes }

We maken een arrow functie aan use... Het is best practise in Vue (en andere JS frameworks) om een functie die als een service dient te prefixen met use.

  • Voeg de lijst van heroes toe aan de service
  • Return de heroes in de service

enkele opmerkingen

  • const heroes op de globale scope -> De data op zich wordt maar 1x gemaakt (dus niet elke keer als useHeroes aanroepen wordt) maar kan alleen gemanipuleerd worden via de useHeroes service die geexposed wordt.

  • useHeroes returned een object van data dus we moeten het object destructuren in de consumerende componenten

  • selectedHero wordt in principe maar 1x gebruikt maar het is proper om alle data met betrekking tot de hero in de service te zetten nu we deze hebben

Bevestig dat alles nog hetzelfde werkt

HIER GESTOPT

Stap 2 - Top heroes

Op dit moment zijn de top heroes in de dashboardview nog hardcoded. We zullen er momenteel vanuit gaan dat de eerste 4 heroes in de lijst de "top heroes zijn". We gaan een functie toevoegen aan de hero.service.ts die de eerste 4 heroes in de lijst teruggeeft als de topheroes.

  • Maak een functie in de useHeroes scope
  • De functie gebruikt een computed value. Een computed value is wanneer de waarde afhankelijk is van een andere variabele. Telkens wanneer deze andere variabele veranderd gaat de computed opnieuw berekend worden. Data die altijd en overal hetzelfde moet zijn wordt in de globale scope gezet zodat het maar 1x aangemaakt wordt, de functies en computed variabelen die die data verwerken worden in de scope van de service geplaatst.
  • Een computed property wordt alleen herberekend als de onderliggende variabele waar het van afhankelijk is veranderen. Het is dus veel efficienter dan een "gewone" methode.
  • Volgende code binnen useHeroes scope
const topHeroes = computed(() => {
return heroes.slice(0, 4);
});
  • export topheroes
  • gebruik in de dashboarview
    • Import
    • voeg v-for toe

Stap 3 - Doorklikken van dashboard naar herodetails

  • Introduceer router in dashboardview die ervoor zorgt dat we kunnen doorklikken naar de details pagina
  • Er is een probleem met navigeren (back button van details gaat altijd naar heroes lijst)
  • Vervangen door router.go(-1)

Stap 4 - Wijzigingen aan hero persisten

  • Werkt al??? Demo (ga naar details, pas iets aan, ga back)
  • Heroes worden nu maar op 1 plaats bijgehouden en omdat we ref gebruiken (2 way binding) en omdat in JS er by reference en niet by value gewerkt wordt (alle plaatsen waar naar object wordt verwezen wijzen naar zelfde value) werkt dit
  • Maar het is niet het gedrag dat we willen -> Save button en de veranderingen mogen alleen opgeslagen worden als gesaved

Stap voor stap

  • HeroDetailsView in initialiseHero hero.value = structuredClone(toRaw(heroes.find((hero) => hero.number === heroID) || null));
  • De toRaw functie is een methode van Vue om een niet-reactieve versie van een reactief object te krijgen. Stel dat heroes een reactieve array is (bijvoorbeeld gemaakt met reactive()), dan zijn alle items daarin ook reactief. Door toRaw() te gebruiken op het gevonden hero-object, krijg je de "ruwe", niet-reactieve versie. Dit voorkomt onbedoelde wijzigingen in de reactiviteit, vooral als je dit hero-object gaat klonen.
  • structuredClone() is een JavaScript-methode die een diepe kopie maakt van een object of array. Het kopieert dus het hele object, inclusief geneste objecten, en isoleert het van het oorspronkelijke object. structuredClone(toRaw(...)) maakt een nieuwe, volledig geïsoleerde kopie van de hero. Dit betekent dat wijzigingen aan de gekloonde hero.value geen invloed hebben op het originele hero-object in heroes.
  • Wat er in hero.value staat is nu een echte kopie van de waarde die in de hero array staat
  • Nieuwe demo om aan te tonen dat het gedrag anders is

  • We verplaatsen de logica van "vind de hero" naar de service want daar zit al onze hero logica samen
  • findHero functie in de useHeroes
const findHero = (id: number): Hero | null => {
return structuredClone(toRaw(heroes.find((hero) => hero.number === id) || null));
}

  • We hebben een functie nodig om de hero te kunnen wijzigen
    const updateHero = (hero: Hero) => {
const index = heroes.value.findIndex((h) => h.number === hero.number);
if (index !== -1) {
heroes.value[index] = structuredClone(toRaw(hero));
}
}
  • heroes array moet een ref worden want we hebben de reactivity van Vue nodig als we de waarden van de array gaan wijzigen (zodat het overal opnieuw gerendered wordt). Als iets een ref is gebeurt dat automagisch.
  • Nodige aanpassingen om de breaking dingen te doen werken
  • Een div met 2 buttons in de herodetailsview
<StyledButton
@click="
updateHero(hero);
router.go(-1);
">
Save
</StyledButton>

....
.buttons {
margin-top: 1rem;
display: flex;
gap: 0.5rem;
}
  • Merk op, meerdere statements gekoppeld aan de click event. Je kan het ook properder maken door in je script een functie te maken die beide doet en die dan te gebruiken in je click event.

DEMO

Stap 5 - Hero verwijderen

  • Een functie in de service om elementen te verwijderen
  • Doe in 2 stappen (eerst niet checken op selectedHero)
const deleteHero = (heroId: number) => {
heroes.value = heroes.value.filter((hero) =>
hero.number !== heroId
);
if (selectedHero.value?.number === heroId) {
selectedHero.value = null;
}
}
  • Delete hero knop in de details view
  • Delete hero knop in de herolist

DEMO

Stap 5 - Props en custom components

UX is belangrijk, de gebruiker denkt liefst zo weinig mogelijk na. Don't make me think

  • Save en delete zouden duidelijker herkenbaar moeten zijn
  • StyledButton een prop geven. Props worden gebruikt om custom components nog herbruikbaarder te maken door alles wat veranderlijk kan zijn afhankelijk van de context uit het component te trekken en door te geven uit de context.

Stap voor stap

  • defineProps toevoegen aan StyledButton waar we een ButtonType aan meegeven die 3 waarden kan hebben (default, negative, primary)
  • conditional class op button definieren die een css class gebruikt afhankelijk van de type
 .primary {
background-color: #1976d2;
color: #ffffff;
}

.primary:hover {
background-color: #1565c0;
color: #ffffff;
}

.negative {
background-color: #c10015;
color: #ffffff;
}

.negative:hover {
background-color: #9b0012;
color: #ffffff;
}
  • defineProps is een vue macro en macros moeten niet expliciet geimporteerd worden, die kan je gewoon direct gebruiken
  • Het geeft aan welke props (properties) we verwachten op ons custom component
  • In dit geval willen we zeggen dat we een prop "type" verwachten en de waarde die meegegeven wordt moet 1 van 3 strings zijn
  • We kunnen de prop een default waarde geven door gebruik te maken van een andere macro withDefaults
withDefaults(defineProps<{ type: ButtonType }>(), { type: "default" })
  • Geeft de juist prop mee aan de juiste knoppen
  • type moet optional gemaakt worden anders heeft de default geen effect (moet je hem nog meegeven)