Les 3: Single Page Applications
Les 3: Single Page Applications
UI Verbeteren
Momenteel worden de segmenten aan de hand van *ngIf directives getoond. Voor een gebruiker is het aangenamer als de navigatie ook via swipes kan gebeuren. Om dit te implementeren kan je de <ion-slides> component gebruiken. Deze component is deprecated in de huidige versie van Ionic en zal in Ionic 7 verwijderd worden. Vanaf Ionic 7 wordt de bibliotheek Swiper.js gebruikt. Je gebruikt natuurlijk geen deprecated componenten in een nieuw project, implementeer de volgende opgaven dus steeds met Swiper. De documentatie over hoe de CSS-files geïmporteerd moeten worden in contradictorisch en niet heel duidelijk, hieronder zie je de nodige SCSS-regels.
Gevaar
De laatste versie van Swiper bevat niet langer Angular componenten, maar is overgestapt op Web Components. Dit zijn componenten die in elk framework gebruikt kunnen worden. De documentatie voor deze laatste release is echter nog heel beperkt, daarbovenop is er nog niet echt voldoende ondersteuning voor web components in WebStorm. Daarom wordt aangeraden om Swiper 8 te gebruiken voor deze oefening. Je vindt de documentatie op https://v8.swiperjs.com/.
In het vervolg van deze opgave gaan we er dan ook van uit dat je versie 8 gebruikt.
@import 'swiper/swiper-bundle.min.css';
swiper {
height: 100%;
}
De segmenten moeten nog steeds correct geselecteerd worden, ook als je navigeert via swipes. Je kan hiervoor de activeIndex property van de SwiperComponent gebruiken. Deze kan vanuit een TypeScript file aangesproken worden door gebruik te maken van de @ViewChild decorator. Deze decorator zoek het eerste voorkomen van een matching component in je template en bewaard een referentie naar deze component in je TS file. Hieronder zie je een voorbeeld, deze code werkt rechtstreeks met de oplossingen voor les 1, je kan deze code ook aanpassen aan jouw programma.
export class Tab1Page {
@ViewChild(SwiperComponent) swiper ?: SwiperComponent;
onActiveIndexChange() {
this.selectedSegment = this.segments[this.swiper.swiperRef.activeIndex]
}
}
Hint
SwiperJS rendert de slides buiten de NgZone (de zone waarin change detection werk). Dit betekent dat property binding niet werkt als je een instantievariabele wijzigt op basis van een event in de slides.
Je kan volgende functie gebruiken om de wijzigingen toch door te drukken, na NgZone te injecteren.
this.ngZone.run(() => methode());
Ook in de omgekeerde volgorde moet dit werken. Maak hiervoor gebruik van het ionChange event en de slideTo methode van de SwiperComponent component.
export class Tab1Page {
@ViewChild(SwiperComponent) swiper ?: SwiperComponent;
segmentChanged(): void {
const i = this.segments.indexOf(this.selectedSegment);
this.swiper.swiperRef.slideTo(i, 500);
}
}
Het is niet eenvoudig om de "delete" knop te gebruiken in combinatie met de slides. Verwijder daarom de sliding options en voeg een nieuwe knop toe aan de list items. In onderstaand screenshot is een <ion-grid> gebruikt om de twee knoppen onder elkaar te tonen.

De nieuwe knop opent onderstaand menu en is gebouwd met een ActionSheet. De optie "Add/Remove from list" doet momenteel nog niets.

Services toevoegen
Verplaats alle code voor het inlezen, updaten, deleten en opvragen van de serie objecten naar een service.
Zonder de geschiedenis objecten ook af in een aparte service. Deze service kan geïnjecteerd worden in de SeriesService, maar niet omgekeerd. Dat zou een circular dependency zijn.
Componenten bouwen
Tijdens de vorige oefeningenreeks heb je zowel in het scherm Series als Next een serie getoond, de lay-out voor beide is heel gelijkaardig. Maak een nieuwe component SeriesItemComponent en gebruik deze in Series en Next.
Voor de kijkgeschiedenis vertoond de lay-out relatief veel verschillend met de andere twee views. Maak hier toch een component voor. Zelfs als de component slechts één keer gebruikt wordt is dit nog steeds veel overzichtelijker.
Stats pagina
Genereer een nieuwe pagina Tab4Page voor de statistieken. Let op dat deze op dezelfde hoogte in je mappenstructuur staat als de andere tabs.

Zorg ervoor dat dit tabblad bereikbaar is als je op de Stats knop drukt. Je zult manueel een nieuwe route moeten toevoegen aan de tabs-routing.module.ts. Omdat de tab4 route automatisch gegenereerd wordt binnen de app-routing-module.ts. Verwijder deze overtollige route alvast.
Bouw onderstaande pagina na, je zult nieuwe functies moeten toevoegen in de SeriesService en/of de Series klasse.
Gebruik ook een nieuwe component StatItemComponent. Deze component heeft 4 inputs, de titel, een ondertitel, de vooruitgang voor de <ion-progress-bar>, en de tekst die onder de progress-bar komt te staan. De tekst onder de progress bar wordt meegegeven als array, elk element in de array wordt op een nieuwe lijn geprint.

Als je exact dezelfde lay-out wil bekomen kan je gebruik maken van onderstaande CSS-klasse, en plaats je de hoofdtitel in een <h2> tag en de tweede titel in een <h3> tag dat binnen een <ion-text> component staat met de kleur medium.
.stats-container {
margin: 1.3em;
max-width: 85vw;
}
Controleer, als je dit scherm af hebt, of de pagina ook echt werkt. Markeer alle afleveringen van Westworld als bekeken in de Series view. De statistieken op je pagina moeten vervolgens automatisch aangepast zijn naar het onderstaande (merk op er staat nu "days" in de plaats van "day"):

Search Bar
Voeg een searchbar toe aan het de series pagina. Als er op het zoek-icoon gedrukt wordt moet de searchbar verschijnen in een popover venster. Dit venster moet 90vw breed zijn. De searchbar kan opnieuw verborgen worden door buiten het popover venster te klikken, of door de "cancel" knop van de <ion-searchbar> te gebruiken.

Gebruik vervolgens 2-way databinding om de filter te laten werken. De zoekfunctie is case-onafhankelijk en zoek op matches in de volledige naam, dus de zoekterm "o" geeft alle series terug, de term "oo" enkel "The Good Place".
Season Page
Maak een nieuwe pagina aan die voor een serie de seizoenen weergeeft. Deze pagina wordt weergegeven als er op de cover van een serie geklikt wordt en toont een overzicht van de seizoenen en de bekeken afleveringen binnen elk seizoen.

Season detail
Maak een nieuwe pagina aan die per seizoen een gedetailleerde weergave geeft voor de aflevering in dat seizoen.
Wisselen tussen afleveringen is opnieuw mogelijk via slides en via de segment knoppen bovenaan.
Deze pagina is bereikbaar via het season view dat je in de voorgaande oefening gemaakt hebt, als je daar op een seizoen drukt wordt de view geopend voor episode van het aangeklikte seizoen. Daarnaast is de pagina ook te bereiken door op een aflevering te klikken in de "Series", "Next" of "Recent" slides. In dit laatste geval wordt de view rechtstreeks geopend op de aangeklikte aflevering.
Hint
Om de juiste slide te tonen (met een specifieke aflevering) kan je gebruik maken van de ionViewDidEnter() lifecycle hook. Deze methode wordt uitgevoerd nadat de view geladen is, in deze methode is een ViewChild dan ook beschikbaar.

Op bovenstaand screenshot zie je dat het IMDB-logo gebruikt wordt, dit logo is niet beschikbaar binnen Ionic Icons, maar wel in FontAwesome. Je kan de nodige iconen installeren via onderstaande pnpm commando's. Meer info over het gebruik van Font Awesome voor Angular vind je op het git repository.
pnpm add @fortawesome/free-solid-svg-icons
pnpm add @fortawesome/free-brands-svg-icons
pnpm add @fortawesome/free-regular-svg-icons
pnpm add @fortawesome/angular-fontawesome
Let op, een aflevering kan geen negatief aantal keer bekeken worden. Als een aflevering 0 keer bekeken is doet de - knop niets.
Alle wijzigingen die doorgevoerd op deze pagina, moeten ook onmiddellijk zichtbaar zijn in de andere views. Als je 4 afleveringen in elk seizoen van "The Good Place" op niet bekeken zet, dan moeten deze afleveringen in de series view zichtbaar worden.
Afbeelding met fallback
Je zult merken dat niet elke afbeelding succesvol opgehaald kan worden, de IMDB API verandert de locatie van de afbeeldingen nogal vaak. Dit betekent dat een aanzienlijk aandeel van de URL's in de dummy data niet meer zullen werken.
Schrijf een nieuwe component ImageWithFallback die 2 input attributen heeft. Het eerste attribuut src heeft dezelfde functie als in een klassiek image tag, het tweede attribuut fallbackSrc wordt gebruikt als de src niet meer online staat. Deze component moet in een afzonderlijke module geplaatst worden zodat deze herbruikt kan worden doorheen je volledige app. Om deze component uit te werken kan je gebruik maken van het error event dat afgevuurd wordt als er iets mis gaat tijdens het laden van een afbeelding. Download de banner placeholder en cover placeholder, zorg er vervolgens voor dat deze getoond worden als respectievelijk de banner en de covers niet geladen kunnen worden.
Hint
Om een afbeelding te gebruiken binnen een Angular project, plaats je deze in de map '/src/assets'. Vervolgens kan de afbeelding gebruikt worden met als src 'assets/afbeeldingNaam'.
Lists
Het derde tabblad bevat de verschillende lijsten die de gebruiker aangemaakt heeft, en toont voor elk van deze lijsten, de series die hieraan toegevoegd zijn. Onderaan deze sectie is een video te vinden die alle functionaliteiten demonstreert.

Via deze pagina kunnen lijsten toegevoegd worden, als er nog geen enkele lijst bestaat, ziet de pagina er als volgt uit.

Als een lijst succesvol aangemaakt wordt, wordt de boodschap "List successfully created!" getoond in een toast met de kleur "success". Als de lijst niet kon aangemaakt worden, wordt ofwel de boodschap "Can't create a list without a name!" of "A list with that name already exists!" getoond in een toast met de kleur "warning".
Via deze pagina kan een bestaande lijst geselecteerd worden, vervolgens worden de series die in deze lijst zitten getoond. Dit kan op 2 manieren. Standaard wordt een list view getoond, dit zijn is dezelfde lay-out als in de basispagina. De alternatieve optie is een grid view, hier worden enkel de covers van de series getoond.

Via de edit knop bovenaan de pagina kan de gebruiker bepalen welke lijsten te zien zijn op het Action Sheet menu in de home pagina. Daarbovenop kan een gebruiker bepalen in welke volgorde deze lijsten verschijnen in het Action Sheet. Om de volgorde te wijzigen kan gebruik gemaakt worden van de <ion-reorder> component. Let op, de documentatie van deze component is niet heel volledig, je zult de source code van dit voorbeeld ook moeten bekijken.

In het hoofdmenu zijn de lijsten te zien in het action sheet, via de knop "Add/Remove from list" wordt een tweede Action Sheet getoond waarop alle lijsten te zien zijn.
