Les 2: Ionic & Angular
Les 2: Ionic & Angular
Tijdens deze lessenreeks leer je de basis van Ionic en Angular. De voorbeeldapplicatie blijft enigszins beperkt. Er wordt geen persistente storage voorzien, ook routing en meerdere pagina's worden pas in een volgende les toegevoegd.
Project aanmaken
BREAKING CHANGES in Ionic 7
Alhoewel onderstaande werkwijze nog steeds geldig is, is de cursus geschreven voor Ionic 6. Ionic 7 is tijdens het academiejaar, op 29/03/2023 uitgekomen en bracht heel veel wijzigingen met zich mee.
Vanaf Ionic 7 is de template voor een Angular project zwaar aangepast. De grootste wijziging is het gebruik van standalone components voor alle pagina's en componenten. Dit is een nieuwe manier om componenten te schrijven die in Angular 14 geïntroduceerd is en die het gebruik van ngmodules onnodig maakt. Alhoewel dit een zeer goede aanpassing is, betekend dit dat grote delen van de cursus niet meer overeenkomen met de laatste versie van Ionic.
In de plaats van onderstaande handleiding te volgen voor het aanmaken van een project kan je de templates vinden op git. Je bent vrij om toch gebruik te maken van Ionic 7, maar dan zal je aanzienlijk meer opzoekwerk moeten verrichten, dit wordt dan ook afgeraden door de lectoren.
Om een project aan te maken kan je het ionic start commando gebruiken.
Hint
Voer dit commando niet uit in een map die met de cloud gesynchroniseerd wordt. Voor één project zijn er duizenden kleine files nodig. Dit vertraagd je cloud storage enorm. Het is ook mogelijk dat je andere problemen ondervindt als je gebruik maakt van een met de cloud gesynchroniseerde map om je project te bewaren. Een git repository met een online remote, is een veel veiligere keuze.
Het eerdergenoemde commando krijgt steeds de naam van het nieuwe project als parameter. Vervolgens wordt er gevraagd om een framework te selecteren. Angular, React en Vue worden momenteel ondersteund door Ionic. Voor deze cursus gebruiken we Angular. Om een nieuw project met de naam 'voorbeeld' aan te maken voer je dus het commando ionic start voorbeeld uit. Tijdens dit process zul je eventueel gevraagd worden om gegevens met Google te delen en om een gratis Ionic account aan te maken. Geen van deze opties heeft invloed op de cursus, kies dus wat jou het beste lijkt.
Pick a framework!
Please select the JavaScript framework to use for your new app.
To bypass this prompt next time, supply a value for the --type option.
? Framework: (Use arrow keys)
> Angular | https://angular.io
React | https://reactjs.org
Vue | https://vuejs.org
De tweede vraag waar Ionic een antwoord op vereist, is de start-template die gebruikt moet worden. Natuurlijk is hier geen vaste keuze, voor deze les (en de meeste andere lessen en oefeningen) gebruiken we de lege template.
Let's pick the perfect starter template!
Starter templates are ready-to-go Ionic apps that come
packed with everything you need to build your app. To bypass this
prompt next time, supply template, the second argument to ionic start.
? Starter template:
tabs | A starting project with a simple tabbed interface
sidemenu | A starting project with a side menu with navigation in the content area
> blank | A blank starter project
list | A starting project with a list
my-first-app | An example application that builds a camera with gallery
conference | A kitchen-sink application that shows off all Ionic has to offer
Nadat de template gekozen is, zal Ionic alle nodige bestanden downloaden. Dit kan enige tijd duren, zeker op trage netwerkverbindingen of als je op een klassieke harde schijf werkt.
Ionic projectstructuur
Als je de map die hierboven aangemaakt is opent in een editor naar keuze (WebStrom, Visual Studio Code, ...), dan zul je zien dat er standaard al een hele hoop bestanden aanwezig zijn. Hieronder worden enkel die bestanden vermeld die belangrijk zijn voor het verdere verloop van de cursus. In onderstaande beschrijvingen staat '/' voor de root directory, de map waarin het project bewaard is.
/package.json
Binnen package.json worden alle geïnstalleerde pakketten opgesomd. Hiervoor worden 2 attributen gebruikt binnen het json object. Het eerste attribuut dependencies bevat een lijst van alle geïnstalleerde pakketten die relevant zijn voor de eindgebruiker. Het tweede attribuut devDependencies bevat een array van alle geïnstalleerde pakketten die enkel relevant zijn tijdens de ontwikkeling van de applicatie. Zaken zoals linters, transpilers, build-tools, en testing libraries, horen hier thuis. Tijdens het compilatieproces worden enkel de dependencies gekopieerd naar de productionbuild. De devDependencies worden hierbij genegeerd.
Hint
Voeg de node_modules map niet toe aan je git repository. Deze map is zeer groot en kan eenvoudig gereproduceerd worden met behulp van package.json. Dit kan, zoals eerder vermeld, met het commando
pnpm install
Stoot je op een probleem tijdens het uitvoeren van pnpm install, dan kan je proberen om pnpm-lock.yaml te verwijderen.
/tsconfig.json
De TypeScript configuratie. Zoals in de vorige les gezien, kunnen instellingen zoals, de JavaScript versie waarnaar getranspileerd wordt aangepast worden in dit bestand. We gebruiken tijdens deze lessenreeks telkens de standaard instellingen. Je kan deze file eventueel aanpassen als je bepaalde linting regels wilt uitzetten.
/src
De src map is de belangrijkste map in het volledige project. Hierin zullen we code schrijven, bestanden aanpassen, ...
/src/index.html
Deze file vormt het ingangspunt van je applicatie. In dit bestand mag enkel de inhoud van het head element aangepast worden. De body blijft steeds onveranderd.
<body>
<app-root></app-root>
<noscript>Please enable JavaScript to continue using this application.</noscript>
</body>
Angular zal de app-root component dynamisch opvullen met de juiste componenten.
/src/global.scss
Dit bestand kan gebruikt worden om SCSS of CSS-regels toe te voegen die voor de volledige applicatie gelden.
/src/main.ts
Het eerste TypeScript bestand dat gelezen en uitgevoerd wordt. Dit bestand wordt door de Angular cli uitgevoerd als we het project starten of compileren en wordt enkel in les 4 aangepast.
/src/theme/variables.scss
Het Ionic theme, standaard bevat dit bestand zowel een light als een dark theme. De kleuren die door een Ionic applicatie gebruikt worden kunnen hier aangepast worden.
/src/environments
Deze map bevat twee files, environment.ts en environment.prod.ts, deze worden respectievelijk gebruikt om globale variabelen te definiëren voor een development en productie build. We gebruiken deze bestanden in les 6 om secrets en andere relevante gegevens voor het communiceren met APIs te bewaren.
/src/assets
Hier kunnen afbeeldingen en andere statische resources geplaatst worden. Dit is echter zelden nodig. Gebruik deze map bij voorkeur niet om JavaScript bibliotheken of fonts toe te voegen aan je project. Hiervoor gebruik je een package-manager, zo worden alle resources in package.json geplaatst.
/src/app
Deze map bevat de eigenlijke code, hierin zullen we dan ook de meeste aanpassingen maken. Deze mappenstructuur kan zelf aangemaakt en aangepast worden. Voor dit vak gebruiken we de door Ionic aanbevolen mappenstructuur. Een applicatie wordt opgedeeld in pagina's, deze pagina's zijn opgebouwd uit, al dan niet gedeelde, componenten en hebben eventueel sub-pagina's. Deze bestanden kunnen gegenereerd worden door het ionic generate [page | module | service | component | module | directive] commando. Hieronder wordt de samenhang tussen de verschillende bestanden in de standaard mappenstructuur uitgelegd.
/src/app/app-routing.module.ts
Hierin worden de routes (URL's) gedefinieerd. Aangezien dit de root component is, worden hier alle root routes beschreven. Dus alles van de vorm /SomePage. Meer info volgt in les 3.
/src/app/app.component.html
De template file voor de app component, hierin wordt de UI gedefinieerd en worden acties aan bepaalde evenementen gekoppeld. Hier wordt ook gedefinieerd hoe data weergegeven wordt. Dit is een HTML-bestand waarin enkele Angular syntax uitbreidingen toegestaan zijn.
/src/app/app.component.scss
De opmaak voor de template file app.component.html. Dit bestand mag zowel CSS als SCSS-code bevatten. Het is echter aan te raden om dit bestand zo min mogelijk aan te passen, Ionic voorziet namelijk heel wat UI-elementen met een adaptieve opmaak, daarnaast zijn er ook een hele hoop CSS utilities beschikbaar en is het dus zelden nodig om zelf CSS-code te schrijven.
/src/app/app.component.ts
Deze file bevat de TypeScript code die data ophaalt, filtert of aanpast. Ook event handlers kunnen hier gedefinieerd worden.
In dit bestand wordt de link gelegd tussen de template en scss files. De decorator @Component op lijn 3, vertelt Angular dat deze file een component definieert. Op lijn 4 wordt de naam van de component gedefinieerd, dit is de naam waaronder deze component in andere componenten gebruikt kan worden. We zullen hier pas volgende les gebruik van maken. Op lijn 5 wordt de template file gekoppeld. Lijn 6 definieert de stylesheets, in tegenstelling tot de vorige attributen, is het mogelijk om meerdere stylesheets te linken aan één component.
import { Component } from '@angular/core';
@Component({
selector: 'app-root',
templateUrl: 'app.component.html',
styleUrls: ['app.component.scss'],
})
export class AppComponent {
constructor() {}
}
/src/app/app.components.spec.ts
Hierin kunnen tests geschreven worden, dit valt buiten de scope van onze cursus.
/src/app/app.module.ts
Dit bestand wordt gebruikt om bibliotheken te importeren en te exporteren. Angular vereist een minimum van één module per project. Wij zullen echter de Ionic guidelines volgen en één module per pagina gebruiken. Modules worden automatisch gegenereerd als we een pagina aanmaken.
We overlopen in onderstaande code de stukken die relevant zijn, niet automatisch gegenereerd kunnen worden, of later aangepast moeten worden.
import {NgModule} from '@angular/core';
import {BrowserModule} from '@angular/platform-browser';
import {RouteReuseStrategy} from '@angular/router';
import {IonicModule, IonicRouteStrategy} from '@ionic/angular';
import {AppComponent} from './app.component';
import {AppRoutingModule} from './app-routing.module';
@NgModule({
declarations: [AppComponent],
entryComponents: [],
imports: [BrowserModule, IonicModule.forRoot(), AppRoutingModule],
providers: [{provide: RouteReuseStrategy, useClass: IonicRouteStrategy}],
bootstrap: [AppComponent],
})
export class AppModule {
}
De declarations array die op lijn 11 gedefinieerd is, wordt gebruikt om aan te geven welke componenten, directives, pipes, ... in deze module zitten. Een component die hier gedefinieerd is, kan in de volledige module gebruikt worden. Momenteel staat hier enkel de AppComponent omdat dit de enige component in de module is, voor de HomePage wordt namelijk een aparte module aangemaakt.
De imports array (lijn 13) wordt gebruikt om modules en bibliotheken te importeren, dit is de array die we het vaakst zullen aanpassen.
De exports array (lijn 14) bevat de componenten, directives, en pipes die in de module gedefinieerd zijn en beschikbaar moeten zijn in andere modules. Als we een module importeren worden alle elementen in de exports array van die module beschikbaar gemaakt. Het is onmogelijk om iets te exporteren dat niet ook gedeclareerd is.
Providers (lijn 14) zijn klassen die geïnjecteerd kunnen worden. We maken hier pas volgende les gebruik van.
De bootstrap array bevat de root component van je volledige applicatie. Dit attribuut wordt enkel in de app.module.ts gedefinieerd. De AppComponent wordt door Angular in het <app-root></app-root/> tag in index.html geplaatst.
/src/app/home/*
De home component bevat dezelfde structuur als de app component. Het is deze component die we tijdens deze les zullen uitbreiden.
To-Do Applicatie
Tijdens deze les worden de elementaire concepten geïllustreerd door een eenvoudige to-do app te ontwikkelen. Gebruikers kunnen taken toevoegen en als afgewerkt markeren. Zoals eerder vermeld, zal deze app nog geen persistente data bevatten.
Een Ionic applicatie kan gestart worden door in de terminal, in de root folder van je applicatie, onderstaand commando uit te voeren.
ionic serve
Binnen webstorm is een terminal geïntegreerd die standaard in de root map van je (WebStorm) project geopend wordt. De knop om deze terminal te openen is links-onderaan te vinden.

Titels
Een Ionic app is bedoeld voor zowel iOS als Android, daardoor moeten sommige dingen dubbel gebeuren. Volgens de iOS guidelines moeten titels waar nadruk op gelegd wordt extra groot gedefinieerd worden. Als er naar beneden gescrold wordt zal de titel kleiner worden en verplaatst worden naar de navigatiebalk. Android heeft zulke richtlijnen niet.

We vervangen alle code in home.page.html met onderstaande code. De titel op lijn 4 is de standaard titel, diegene die op lijn 13 is de grote iOS titel. Hier zorgt het attribuut collapse="condense" (lijn 10) er voor dat de grote titel niet getoond als je naar beneden scrolt.
<ion-header [translucent]="true">
<ion-toolbar>
<ion-title>
To-Do
</ion-title>
</ion-toolbar>
</ion-header>
<ion-content [fullscreen]="true">
<ion-header collapse="condense">
<ion-toolbar>
<ion-title size="large">
To-Do
</ion-title>
</ion-toolbar>
</ion-header>
</ion-content>
Task datatype
We bouwen een To-Do app, dus is er nood aan een Task datatype. We hebben dus een interface of class nodig. In dit geval willen we enkel data bijhouden en geen complexe bewerkingen uitvoeren op deze data en is een interface voldoende. Merk op dat we op lijn 1 het export statement toegevoegd hebben, zonder dit statement is het onmogelijk om de interface in de rest van de code te gebruiken.
export interface Task {
name: string;
id: number;
done: boolean;
}
Copy constructor
Het is niet altijd voldoende om een interface te gebruiken (in deze les is een interface voldoende). Als je type methodes nodig heeft om de data te bewerken, dan is een klasse een betere keuze. Het is echter ook niet altijd voldoende om enkel een klasse te maken. Stel dat je communiceert met een document database, je wil de data die je terugkrijgt kunnen manipuleren en terugsturen naar de database. Om die manipulaties uit te voeren wil je methodes toevoegen aan je klasse. Maar de data die je binnenkrijgt van de database is in JSON formaat en bevat geen methodes, of andere zaken die enkel in een class beschikbaar zijn. Om dit probleem op te lossen (en toch overal een type te voorzien) kunnen we een klasse maken die de interface gebruikt om een nieuwe instantie aan te maken.
De constructor die we hieronder gebruiken is een zogenaamde copy-constructor, dit is een constructor die niets anders doet dan gegevens kopiëren van een bestaand object naar de nieuwe instantie van het object dat de constructor bevat. In dit geval worden alle gegevens uit een object dat de interface ITask implementeert gekopieerd naar this, i.e. de nieuwe instantie van de Task klasse.
Let op, we hebben geen implements gebruikt omdat dit het onmogelijk maakt om één van de velden in de klasse op private in te stellen.
De uitroeptekens achter de properties in de klasse Task, geven aan de TypeScript compiler aan dat wij als programmeur zeker zijn dat deze properties correct geïnitialiseerd zijn. Zonder deze non-null assertion operator, geeft de TypeScript compiler in de nieuwste versies fouten. Ook al zien wij duidelijk dat de properties uit de ITask interface gekopieerd worden en dat alle instantievariabelen in de Task klasse dus ook correct geïnitialiseerd zijn.
export interface ITask {
name: string;
id: number;
done: boolean;
}
export class Task {
done!: boolean;
id!: number;
name!: string;
constructor(obj: ITask) {
Object.assign(this, obj);
}
}
Begrip: Classes & Interfaces
De keuze tussen class en interface maak je op basis van vereiste functionaliteit. Heeft je type enkel een aantal attributen, dan is een interface de beste keuze. Heeft je type ook methoden nodig, dan gebruik je best een klasse.
Taken tonen
Waarschuwing
We gebruiken in het verdere verloop van deze les, en in de volgende les een in-memory database zonder persistentie om de scope van de voorbeelden te beperken. Als je dit doet in je project, verlies je hier veel punten door. Maak gebruik van één van de manieren om data persistent te bewaren die verder in de cursus besproken worden.
We willen een lijst van taken tonen op de home pagina (home.page.html) van de applicatie. Het is dus nodig om de template file aan te passen, maar voor we hieraan kunnen beginnen moeten we een lijst taken hebben om te tonen.
Aangezien de data op home.page.html getoond moet worden, is het nodig om de lijst taken te bewaren in de bijhorende logica file home.page.ts. We definiëren hier een lege array taskList (lijn 11) en een integer id die we op 0 initialiseren. Deze instantievariabele zal een vergelijkbare functie hebben als een primary key in een relationele database. De # voor de variabele naam duid aan dat deze private is en is te verkiezen bouwen een private access modifier omdat deze modifier at-runtime geen betekenis meer heeft, terwijl de # modifier een deel uitmaakt van de JavaScript specificaties en at-runtime ook private is (al kan dit eventueel wel kleine gevolgen hebben voor de snelheid van de app).
import {Component} from '@angular/core';
import {Task} from 'src/datatypes/task';
import {AlertController} from '@ionic/angular';
@Component({
selector: 'app-home',
templateUrl: 'home.page.html',
styleUrls: ['home.page.scss']
})
export class HomePage {
taskList: Task[] = [];
#id = 0;
constructor() {
}
}
We hebben (nog) geen API of persistente storage, voorlopig werken we dus met vluchtige data, data die verdwijnt nadat de pagina herladen wordt. Om de UI te bouwen is het handig als er al enkele taken aanwezig zijn. We voegen in de constructor een lus toe die 50 test taken toevoegt, elke taak met een even id wordt als afgewerkt gemarkeerd.
constructor() {
for (let i = 0; i < 50; i++) {
this.taskList.push({
name: 'Task ' + i,
done: this.#id % 2 === 0,
id: this.#id
});
this.#id++;
}
}
Om de taken te tonen gebruiken we een ion-list. Angular bevat het *ngFor directive dat toegevoegd kan worden aan een HTML-element of Angular component om het een aantal keren te herhalen.
<ion-list>
<ion-item *ngFor='let task of taskList'>
<ion-label>
{{task.name}}
</ion-label>
</ion-item>
</ion-list>
Zoals in de voorgaande code, op lijn 2, te zien is, krijgt het *ngFor directive een normale JavaScript ES6 lus als argument. Deze lus loopt over alle taken in de array taskList, die gedefinieerd is in de logica file home.page.ts. Binnen de <ion-item> component kan de variabele task gebruikt worden.
Begrip: *ngFor Directive
Het *ngFor directive kan gebruikt worden om over een iterable te itereren in een template. Dit directive wordt als attribuut meegegeven aan een component of HTML-element. De syntax is als volgt:
<component *ngFor="let variable of iterable">
<!--Scope of the variable-->
</component>
Op lijn 4 gebruiken we string interpolation, dit stelt ons in staat een variabele als string uit te printen, merk op dat variabele die uitgeprint wordt omgeven is door dubbele accolades.
Begrip: String Interpolation
String interpolation kan gebruikt worden om in de template een string of expressie waarvan het resultaat naar een string geconverteerd kan worden, uit te printen. Wijzigingen in de variabele zijn onmiddellijk zichtbaar in de template. String interpolation wordt aangeduid met dubbele accolades.

Taken afwerken
We hebben nu een lijst van taken, maar de optie om een taak als afgewerkt te markeren ontbreekt nog. We zullen een lege cirkel gebruiken om een niet-afgewerkte taak aan te geven, een afgewerkte taak wordt aangeduid met een checkmark op een ingekleurde cirkel. Om dit te implementeren kunnen we gebruik maken van Ion Icons
Afhankelijk van de status van de taak moet een ander icoon getoond worden, Angular bied hiervoor een oplossing. Het *ngIf directive kan gebruikt worden om elementen conditioneel te renderen.
<ion-list>
<ion-item *ngFor='let task of taskList'>
<ion-label>
{{task.name}}
</ion-label>
<ion-icon *ngIf='task.done' name='checkmark-circle'></ion-icon>
<ion-icon *ngIf='!task.done' name='ellipse-outline'></ion-icon>
</ion-item>
</ion-list>
Begrip: *ngIf Directive
Het *ngIf directive kan gebruikt worden om voorwaardelijk te renderen. Dit directive wordt als attribuut meegegeven aan een component of HTML-element. De syntax is als volgt:
<component *ngIf="boolean expression">...</component>
Else tak
In de plaatst van verschillende *ngIf directives na elkaar te gebruiken, is het ook mogelijk om een else tak toe te voegen. Hiervoor moet de code die in de else tak getoond moet worden, omringt worden door een <ng-template> component. De component krijgt vervolgens een naam (notCompleted) in onderstaand voorbeeld. We kunnen deze naam tenslotte gebruiken in het *ngIf directive.
<ion-icon *ngIf='task.done; else notCompleted'
name='checkmark-circle'
(click)='toggleTaskStatus(task.id)'></ion-icon>
<ng-template #notCompleted>
<ion-icon name="ellipse-outline"
(click)='toggleTaskStatus(task.id)'></ion-icon>
</ng-template>
Momenteel heeft elke afgewerkte taak een zwarte achtergrond, dit is niet bijzonder aantrekkelijk of duidelijk. Ionic voorziet een aantal basiskleuren, we gebruiken de kleur 'success' om de achtergrond van een afgewerkte taak groen te maken.
<ion-icon *ngIf='task.done' name='checkmark-circle' color='success'></ion-icon>

De status van de taak is nu duidelijk zichtbaar, nu rest enkel nog het afwerken van een taak. Hiervoor gebruiken we event binding, meer specifiek linken we het click event aan een nieuwe methode toggleTaskStatus in de logica file.
<ion-icon *ngIf='task.done' name='checkmark-circle' color='success'
(click)='toggleTaskStatus(task.id)'></ion-icon>
<ion-icon *ngIf='!task.done' name='checkmark'
(click)='toggleTaskStatus(task.id)'></ion-icon>
toggleTaskStatus(id: number): void {
const task = this.taskList.find(t => t.id === id);
if (task) {
task.done = !task.done;
}
}
Begrip: Event Binding
Event binding kan gebruikt worden om te reageren op acties van de gebruiker, events zoals click en change worden zeer frequent gebruikt. Het event wordt steeds omringt door ronde haken. Event binding wordt gedefinieerd in de template, de syntax is als volgt:
<component (event)="methodeInLogicaFile()">...</component>
Nieuwe taken toevoegen
Om nieuwe taken toe te voegen maken we gebruik van een floating action button (FAB). We voegen deze knop rechts onderaan toe, hiervoor stellen we, zoals in de documentatie te lezen is, het attribuut vertical in op 'bottom' en het attribuut horizontal op 'end'. De knop wordt in de <ion-content> component in home.page.html toegevoegd.
<ion-fab vertical='bottom' horizontal='end' slot='fixed'>
<ion-fab-button>
<ion-icon name='add'></ion-icon>
</ion-fab-button>
</ion-fab>
Property binding
Het is mogelijk om via property binding attributen een waarde te geven vanuit de logica file. Hieronder passen we de FAB aan zodat deze zijn verticale positie vanuit de logica file krijgt. Vervolgens schrijven we een methode die deze waarde elke 250 milliseconden aanpast. Dit demonstreert de kracht van property binding, maar heeft verder geen nut in onze applicatie, in het uitgewerkte lesvoorbeeld staat dit dan ook in commentaar.
<ion-fab [vertical]='verticalFabPosition' horizontal='end' slot='fixed'>
<ion-fab-button>
<ion-icon name='add'></ion-icon>
</ion-fab-button>
</ion-fab>
export class HomePage {
verticalFabPosition = 'bottom';
constructor() {
// Rest van de code is verborgen.
setInterval(
() => {
this.verticalFabPosition =
this.verticalFabPosition === 'bottom' ? 'top' : 'bottom';
},
250
);
}
// Rest van de code is verborgen.
}
Begrip: Ternary
Een ternary operator kan gebruikt worden om een verkorte if-then-else opdracht te schrijven. Onderstaande twee voorbeelden, hebben een volledig identieke werking.
let resultaat;
if (booleanExpression) {
resultaat = "Waar";
} else {
resultaat = "Niet waar";
}
const resultaat = booleanExpression ? "Waar" : "Niet waar";
Begrip: Property binding
Property binding kan in de template gebruikt worden door vierkante haken rond een attribuut te plaatsen. De waarde van dit attribuut komt vervolgens uit een variabele in de logica file. Als de variabele aangepast wordt in de logica file, zijn deze wijzigingen ogenblikkelijk zichtbaar in de UI.
<component [attribuut]="variabeleInLogicaFile">...</component>;
Alert
We zullen gebruik maken van een popup venster om de nieuwe taak aan te maken. Dit is geen goede UX keuze voor complexere data, we gebruiken dit hier enkel omdat we nog geen routing gezien hebben. Gebruik een Alert enkel voor ja/nee vragen, of input waar maximaal één veld voor nodig is.
In de documentatie is te zien dat we een AlertController nodig hebben, deze kan op twee manieren toegevoegd worden aan de klasse HomePage. Beide opties vereisen dat we de AlertController injecteren in de constructor van de klasse HomePage.
import { AlertController } from '@ionic/angular';
export class HomePage {
// Niet relevante code weggelaten.
constructor(alertController: AlertController) {}
}
Concept: Dependency Injection
Dependency injection is het process waarbij een dependency meegeven wordt als parameter aan de constructor van een component, pagina of service. Angular detecteert dat de parameter hier staat en voegt deze automatisch toe als de component, pagina of service geïnstantieerd wordt.
De eerste, klassieke, optie is om een instantievariabele alertController aan te maken en de geïnjecteerde dependency hieraan toe te wijzen.
import { AlertController } from '@ionic/angular';
export class HomePage {
alertController: AlertController;
// Niet relevante code weggelaten.
constructor(alertController: AlertController) {
this.alertController = alertController;
}
}
We kunnen de dependency ook een access modifier (public, private) geven. Zo zorgt Angular er automatisch voor dat de AlertController beschikbaar is in de rest van de klasse. De keuze tussen private en public wordt bepaald door van de acties die in de template moeten gebeuren. Wil je de geïnjecteerde dependency in de template kunnen gebruiken, dan kies je voor public, anders voor private.
import { AlertController } from '@ionic/angular';
export class HomePage {
// Niet relevante code weggelaten.
constructor(private alertController: AlertController) {}
}
Op basis van de documentatie komen we tot volgende code om een alert weer te geven.
async presentAlert(): Promise<void> {
const alert = await this.alertController.create({
header: 'New Task',
buttons: [
{
text: 'Cancel',
role: 'cancel',
cssClass: 'secondary'
}, {
text: 'OK',
handler: (inputs) => {
this.newTask(inputs.name);
}
}
],
inputs: [
{
name: 'name',
type: 'text',
placeholder: 'Task Description'
}
]
});
await alert.present();
}
newTask(name: string): void {
this.taskList.push({
name,
id: this.#id,
done: false
});
this.#id++;
}
Begrip: Asynchrone functies
Het async keyword betekend dat de functie niet stap voor stap uitgevoerd word. Alles zonder een await keyword wordt sequentieel, vlak achter elkaar, uitgevoerd. Een lijn die het async keyword bevat, duur langer om uit te voeren (meestal omdat externe bronnen geconsulteerd moeten worden) en moet niet afgewerkt zijn voordat een andere methode uitgevoerd kan worden. Er kunnen dus eventueel andere taken, die niet in deze functie staan, uitgevoerd worden. Als de asynchrone taak klaar is, wordt het volgende stukje code, na de await uitgevoerd, de asynchrone functie gaat dan dus verder.
Een async methode geeft steeds een Promise<T> terug. Een Promise heeft een type T, het is vanzelfsprekend dat dit type alles kan zijn. Een string, number, boolean, een zelfgemaakt type, of, in dit geval, void.
Tenslotte moet de FAB button nog aangepast worden zodat deze de methode presentAlert() oproept.
<ion-fab [vertical]='verticalFabPosition' horizontal='end' slot='fixed'>
<ion-fab-button (click)='presentAlert()'>
<ion-icon name='add'></ion-icon>
</ion-fab-button>
</ion-fab>