Les 3: Single Page Applications

Sebastiaan HenauOngeveer 7 minuten

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>open in new window 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.jsopen in new window 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 Componentsopen in new window. 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/open in new window.

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>open in new window gebruikt om de twee knoppen onder elkaar te tonen.

Figuur 1: Nieuwe menu knop

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

Figuur 2: Series menu

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.

Figuur 3: Tab 4

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>open in new window, 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.

Figuur 4: StatItemComponent

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>open in new window 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"):

Figuur 5: Stats Pagina

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>open in new window te gebruiken.

Figuur 6: Searchbar

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.

Figuur 7: Season view

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 11 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.

Figuur 8: Series Detail

Op bovenstaand screenshot zie je dat het IMDBopen in new window-logo gebruikt wordt, dit logo is niet beschikbaar binnen Ionic Iconsopen in new window, maar wel in FontAwesomeopen in new window. 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 repositoryopen in new window.

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.

Figuur 9: Gebruik van de series detail pagina

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.

Figuur 10: Lists - Eindresultaat

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

Figuur 11: List pagina zonder list

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.

Figuur 12: Grid & List view

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>open in new window component. Let op, de documentatie van deze component is niet heel volledig, je zult de source codeopen in new window van dit voorbeeld ook moeten bekijken.

Figuur 13: List menu

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.

Figuur 14: Action Sheet
Figuur 15: List demo
Laatst geüpdate:
Bijdragers: Sebastiaan Henau