6. Progressive Web Apps & Lifecycle
6. Progressive Web Apps & Lifecycle
Tijdens deze laatste les bekijken we hoe we een applicatie kunnen publiceren als progressive web app. Daarnaast bekijken we welke Ionic en Angular lifecycle methodes er bestaan en hoe deze gebruikt kunnen worden om bugs op te lossen en de app gebruiksvriendelijker te maken.
Voor deze les zijn geen oefeningen voorzien.
Progressive web apps
Zoals in de inleiding besproken, is een progressive web app (PWA) een applicatie die als website aangeboden wordt. Via deze website kan de applicatie vervolgens geïnstalleerd worden op een native toestel of op een desktop/laptop computer.
Om een app aan te bieden als PWA zijn twee zaken nodig. Ten eerste moet er een service worker aangemaakt worden. Ten tweede moet een geldig web App Manifest toegevoegd worden aan de webapplicatie.
Service worker
Voor een PWA is de service worker een proxy die tussen de app en het internet staat. Een service worker heeft als taak het voorzien van een aangename en functionele offline-ervaring. Dit betekent dat de service worker elk netwerk request onderschept en één van twee mogelijke dingen doet, afhankelijk van de situatie.
De app is verbonden met het netwerk: Het HTTP request wordt afgehandeld op de klassieke manier, i.e. het wordt verstuurd naar de server. Het antwoord wordt vervolgens (door de service worker) bewaard in de cache, en doorgegeven aan de app.
De app is niet verbonden met het netwerk: Het HTTP request wordt afgebroken door de service worker en dus niet verder gestuurd naar de webserver. De service worker beantwoord het request van de applicatie met gecachete data.
Naast deze functionaliteiten, zorgt een service worker er ook voor dat de laatste assets gedownload worden als de applicatie verbonden is met het internet. Zo worden de laatste wijzigingen steeds weergegeven in de applicatie, zonder dat de gebruiker de app moet bijwerken.
Een service worker is niet beschikbaar op onbeveiligde verbindingen, HTTPS is dus vereist. Daarnaast is het onmogelijk om een service worker te gebruiken als de website in privénavigatie uitgevoerd wordt.
Web app manifest
Een web app manifest is een .json file die de eigenschappen van de app beschrijft. Zo zijn de naam, een beschrijving van de app, de iconen en de URL die getoond moet worden tijdens het opstarten gedefinieerd in het manifest.
PWAs & Angular
Angular voorziet, net als de andere grote frameworks, een library waarmee een Angular project omgevormd kan worden in een PWA. Deze bibliotheek kan geïnstalleerd worden via ng add. Dit commando voert, naast het installeren van pakket, ook nog een post-install script uit. Dit script zorgt voor het merendeel van de nodige configuratie in het Angular project.
Waarschuwing
In het onderstaande commando moet je ANGULAR_MAJOR_VERSION_NUMBER vervangen met de versie van het Angular package dat in je package.json staat. Als in package.json de versie 16.2.7 staat, dan gebruik je in het ng add commando het versienummer ^16.0.0.
ng add @angular/pwa@^MAJOR_ANGULAR_VERSION_NUMBER.0.0Dit script maakt een aantal bestanden aan die hieronder besproken worden. De configuratie is gelijk voor elke applicatie, in het vervolg van de les wordt gebruik gemaakt van de oplossingen voor les 5.
ngsw-config.json
Dit bestand, dat in de root map van je project gegenereerd is, beschrijft de configuratie van de service worker. We bespreken de inhoud van dit bestand niet aangezien dit voornamelijk over optimalisaties gaat en een gedetailleerde bespreking van de opties te diepgaand is voor deze cursus. De geïnteresseerde lezer kan de verschillende opties bekijken op https://angular.io/guide/service-worker-config.

Dit configuratiebestand beschrijft hoe de service worker werkt, dus moet deze configuratie in de Angular app ingeladen worden. Dit gebeurt (automatisch via ng add) in app.module.ts.
import {ServiceWorkerModule} from '@angular/service-worker'
@NgModule({
declarations: [AppComponent, MenuChannelGroupComponent],
imports: [BrowserModule, IonicModule.forRoot(), AppRoutingModule,
provideFirebaseApp(() => initializeApp(environment.firebaseConfig)),
provideAuth(() => getAuth()),
provideFirestore(() => initializeFirestore(getApp(), {
localCache: persistentLocalCache({tabManager: persistentMultipleTabManager()})
})),
ServiceWorkerModule.register('ngsw-worker.js', {
enabled: !isDevMode(),
// Register the ServiceWorker as soon as the application is stable
// or after 30 seconds (whichever comes first).
registrationStrategy: 'registerWhenStable:30000'
}),
],
providers: [{provide: RouteReuseStrategy, useClass: IonicRouteStrategy}],
bootstrap: [AppComponent],
})
export class AppModule {}manifest.webmanifest
Het web manifest wordt ook automatisch aangemaakt door het ng add commando en bevind zich in de src folder en wordt automatisch gelinkt in index.html.

De standaard inhoud van dit bestand moet, zoals hieronder te zien, duidelijk aangepast worden.
Info
De bestaande iconen in het manifest worden niet overschreven door de tool die we gebruiken om de iconen te genereren. Verwijder deze defaults dus eerst.
{
"name": "app",
"short_name": "app",
"theme_color": "#1976d2",
"background_color": "#fafafa",
"display": "standalone",
"scope": "./",
"start_url": "./",
"icons": [
{
"src": "assets/icons/icon-72x72.png",
"sizes": "72x72",
"type": "image/png",
"purpose": "maskable any"
},
...
]
}<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="utf-8"/>
<title>Backend | Les 6 | Voorbeeld</title>
<base href="/"/>
<meta name="color-scheme" content="light dark"/>
<meta name="viewport" content="viewport-fit=cover, width=device-width, initial-scale=1.0, minimum-scale=1.0, maximum-scale=1.0, user-scalable=no"/>
<meta name="format-detection" content="telephone=no"/>
<meta name="msapplication-tap-highlight" content="no"/>
<link rel="icon" type="image/png" href="assets/icon/favicon.png"/>
<!-- add to homescreen for ios -->
<meta name="apple-mobile-web-app-capable" content="yes"/>
<meta name="apple-mobile-web-app-status-bar-style" content="black"/>
<link rel="manifest" href="manifest.webmanifest">
<meta name="theme-color" content="#1976d2">
</head>
<body>
<app-root></app-root>
<noscript>Please enable JavaScript to continue using this application.</noscript>
</body>
</html>theme_color
Dit attribuut wordt gebruikt door sommige besturingssystemen en browsers om het UI aan te passen zodat dit meer overeenkomt met de PWA. Zo wordt de adresbalk in Google Chrome (Android), bijvoorbeeld aangepast.

Bron: https://developers.google.com/web/updates/2015/08/using-manifest-to-set-sitewide-theme-color
background_color
Dit attribuut wordt gebruikt om de achtergrondkleur van de applicatie in te stellen terwijl de stylesheets aan het laden zijn, deze achtergrondkleur wordt niet meer gebruikt nadat de PWA volledig geladen is.
name & short_name
Een PWA moet 2 namen definiëren, de eerste naam property name zal zoveel mogelijk gebruikt worden. Maar, het is natuurlijk mogelijk dat deze te lang is om volledig weer te geven, in dat geval wordt de short_name gebruikt.
Onderstaande video demonstreert hoe de PWA geïnstalleerd kan worden op Android, via een Chromium gebaseerde browser. Op het splash screen (rond 00:16) wordt de lange naam weergegeven, in de app-drawer (overzicht van alle apps, rond 00:14), wordt de korte naam gebruikt. Ook de theme_color (#1976d2) is duidelijk te zien in statusbalk rond (00:20).
display
Dit attribuut wordt gebruikt om te bepalen hoe de applicatie uitgevoerd wordt als PWA. Er zijn vier mogelijke opties, deze lopen van helemaal geen browser elementen tot een volledige browser ervaring.
De optie fullscreen opent de applicatie als fullscreen app en toont geen UI-elementen uit de browser. Deze optie werkt momenteel niet in chromium gebaseerde browsers. Het bijhorende issue staat al meer dan 3 jaar open, de kans is dus klein dat deze modus snel ondersteuning zal krijgen. Voorlopig wordt de optie standalone als fallback gebruikt.
De optie standalone kan gebruikt worden om een native app te imiteren, dit is de meestgebruikte optie. Er worden geen UI-elementen uit de browser getoond en de gebruiker kan de app minimaliseren, maximaliseren, van grootte veranderen, openen vanop de taakbalk, ... Dit kan enkel op Windows, macOs en *nix systemen. Op Android of iOS wordt de app met deze optie natuurlijk geopend als een fullscreen app, dit is tenslotte de manier waarop native apps getoond worden op mobiele operating systems. In onderstaande video is het resultaat van de eigenschap background_color te zien; rond 00:15, wordt de kleur #fafafa gebruikt terwijl de app aan het laden is.
Via de optie minimal-ui heeft de app nog steeds dezelfde functionaliteiten als bij de optie standalone, er worden echter ook een minimaal aantal UI-elementen voor navigatie getoond, zoals bijvoorbeeld de "reload" en "back" knoppen.
scope
Via dit attribuut kunnen we de paden die bezocht mogen worden in de PWA beperken. De URL is relatief ten opzichte van de locatie van het manifest. Zo zou een pad als '/tabs/tab1', de gebruiker beperken tot het eerste tabblad in de app. Als de gebruiker dan navigeert naar '/', wordt de URL geopend in een webbrowser en niet langer in de PWA. Normaal gezien moet deze optie niet aangepast worden omdat de volledige applicatie als PWA gebruikt kan worden.
start_url
De URL die getoond wordt als de applicatie gestart wordt, ook deze property moet normaal gezien niet aangepast worden.
icons
De icons array bevat de verschillende icoontjes waaruit, op basis van de dpi van het toestel wordt het beste icoon uit deze array gekozen. Minimaal zijn er twee iconen nodig, een icoon van 192x192 en van 512x512. Op basis van deze resoluties voert een browser eventueel conversies uit om een gepast icoon te genereren. Via de tool pwa-assets-generator kunnen we uit hetzelfde icoon als voor de Android app, de nodige files genereren.
Het onderstaand commando genereert de nodige icoontjes en voegt deze automatisch toe aan het manifest en de indexpagina. Dit laatste is nodig voor iOS toestellen omdat Apple de Web-API Specs momenteel niet goed ondersteund. We voegen het commando eerst toe aan de package.json en roepen het vervolgens op.
{
"name": "backend_lecture5_example_start",
"version": "0.0.1",
"author": "Ionic Framework",
"homepage": "https://ionicframework.com/",
"scripts": {
"ng": "ng",
"start": "ng serve",
"build": "ng build",
"test": "ng test",
"lint": "ng lint",
"e2e": "ng e2e",
"android": "ionic build --prod && pnpm exec cap sync android && pnpm exec cap open android",
"android-resources": "capacitor-assets generate --iconBackgroundColor #eeeeee --iconBackgroundColorDark #222222 --splashBackgroundColor #eeeeee --splashBackgroundColorDark #111111 --logoSplashScale 1 --android",
"pwa-resources": "pwa-asset-generator ./resources/icon.png ./src/assets/icons -m ./src/manifest.webmanifest -i ./src/index.html",
"ios-resources": "capacitor-assets generate --iconBackgroundColor #eeeeee --iconBackgroundColorDark #222222 --splashBackgroundColor #eeeeee --splashBackgroundColorDark #111111 --ios",
"gen-resources": "pnpm run ios-resources && pnpm run android-resources",
"android-live": "ionic cap run android -l --external",
"android-run": "ionic build --prod && pnpm exec cap sync android && pnpm exec cap run android",
"android-run-default": "ionic build --prod && pnpm exec cap sync android && pnpm exec cap run android --target ADD_YOUR_OWN_DEVICE_ID_HERE"
},
.
.
.
}App publiceren PWA via Firebase
Om de applicatie te publiceren als PWA is relatief weinig extra werk nodig, het grootste deel kan weer geautomatiseerd worden door middel van een command-line tool.
De eerste keer dat je deze tool gebruikt, moet je inloggen op Firebase. Dit kan via het commando
firebase loginVervolgens kan deze tool gebruikt worden om te verbinden met een Firebase project. Via het onderstaande commando's kunnen we alle configuratiestappen doorlopen.
firebase initPS lecture-5-firebase> firebase init
######## #### ######## ######## ######## ### ###### ########
## ## ## ## ## ## ## ## ## ## ##
###### ## ######## ###### ######## ######### ###### ######
## ## ## ## ## ## ## ## ## ## ##
## #### ## ## ######## ######## ## ## ###### ########
You're about to initialize a Firebase project in this directory:
C:\Projects\mobileapplicationsv2\examples\mobile_lecture7_example
Before we get started, keep in mind:
* You are currently outside your home directoryNa y ingegeven te hebben, verschijnt een menu waarmee we verschillende Firebase functies kunnen activeren. Hier kies je voor hosting.
? Which Firebase features do you want to set up for this directory? Press Space to select features, then Enter to confirm your choices.
( ) Realtime Database: Configure a security rules file for Realtime Database and (optionally) provision default instance
( ) Firestore: Configure security rules and indexes files for Firestore
( ) Functions: Configure a Cloud Functions directory and its files
>(*) Hosting: Configure files for Firebase Hosting and (optionally) set up GitHub Action deploys
( ) Hosting: Set up GitHub Action deploys
( ) Storage: Configure a security rules file for Cloud Storage
( ) Emulators: Set up local emulators for Firebase productsVervolgens wordt er gevraagd of je een nieuw (Firebase) project wil aanmaken of een bestaand project wil gebruiken. Als je de webinterface gebruikt hebt om het project te configureren (zoals in les 5), dan kies je voor "Use an existing project", als je dit nog niet gedaan hebt, kies je voor "Create a new project".
First, let's associate this project directory with a Firebase project.
You can create multiple project aliases by running firebase use --add,
but for now we'll just set up a default project.
? Please select an option: (Use arrow keys)
> Use an existing project
Create a new project
Add Firebase to an existing Google Cloud Platform project
Don't set up a default projectVervolgens moet de public directory ingegeven worden. Dit is de map waar het gecompileerde webproject in zit. Aangezien het commando ionic build --prod gebruikt maakt van de map www, moeten we de default optie overschrijven.
=== Hosting Setup
Your public directory is the folder (relative to your project directory) that
will contain Hosting assets to be uploaded with firebase deploy. If you
have a build process for your assets, use your build's output directory.
? What do you want to use as your public directory? (public) wwwVervolgens vraagt Firebase of je de hosting wilt configureren voor single-page applications. Dit houdt in dat als je de browser opent en een URL als https://project.web.app.com/tabs/tab1 invoert, er niet geprobeerd zal worden om een bestand https://project.web.app.com/tabs/tab1/index.html te openen. In plaats daarvan zal het verzoek afgehandeld worden door de enige HTML-pagina in je volledige project ('https://project.web.app.com/index.html'). Kies hier dus voor y.
? Configure as a single-page app (rewrite all urls to /index.html)? (y/N) yVervolgens kan je automatische builds configureren die uitgevoerd worden als je een nieuwe commit pusht naar een bepaalde branch in een git repository. Dit ligt buiten de scope van de cursus, maar je bent natuurlijk vrij om zo'n pipeline op te zetten.
? Set up automatic builds and deploys with GitHub? (y/N) NTenslotte krijg je eventueel de vraag om een bestaand indexbestand te overschrijven. Kies hier voor N, deze index is tenslotte een gecompileerde versie van je project.
? File www/index.html already exists. Overwrite? (y/N)Nu de configuratie afgehandeld is, kan je de applicatie publiceren via Firebase hosting. Om zeker te zijn dat dit de laatste versie is, maak je best nog een nieuwe build aan.
ionic build --prod
firebase deployHet resultaat ziet er ongeveer als volgt uit.
=== Deploying to 'les6-9b012'...
i deploying hosting
i hosting[les6-9b015]: beginning deploy...
i hosting[les6-9b015]: found 1441 files in www
+ hosting[les6-9b015]: file upload complete
i hosting[les6-9b015]: finalizing version...
+ hosting[les6-9b015]: version finalized
i hosting[les6-9b015]: releasing new version...
+ hosting[les6-9b015]: release complete
+ Deploy complete!
Project Console: https://console.firebase.google.com/project/les6-9b012/overview
Hosting URL: https://les6-9b012.web.appDe app kan vervolgens geïnstalleerd worden vanop deze URL. Hoe dit gebeurt, is hierboven gevisualiseerd bij de beschrijvingen van de verschillende mogelijkheden voor de displayopties.
Lifecycle
Ionic & Angular voorzien enkele lifecycle methodes die gebruikt kunnen worden om acties uit te voeren op een bepaald moment tijdens de levensduur van een component. De scroll-positie kan bijvoorbeeld aangepast worden als de view geladen is, of een formulier kan leeg gemaakt worden als de component herbruikt wordt. Daarnaast kunnen de lifecycle methodes ook gebruikt worden om subscriptions op te ruimen, zodat er geen memory leaks ontstaan.
Ionic lifecycle methodes
Ionic voorziet enkele lifecycle methodes die meerdere keren uitgevoerd kunnen worden doorheen de levensduur van een component. Af en toe herbruikt Ionic een component, in zo'n geval kunnen bugs ontstaan. Een formulier dat herbruikt wordt kan bijvoorbeeld restanten van het vorige gebruik, zoals ingevulde formuliervelden, bevatten.
Voor deze en andere mogelijke bugs zijn de 4 Ionic lifecycle methodes een perfecte oplossing. Deze methodes kunnen in elke component/pagina gebruikt worden zonder extra imports toe te voegen.
ionViewWillEnter
De ionViewWillEnter methode kan gebruikt worden om data te initialiseren die voor elk gebruik van de component opnieuw ingeladen of gereset moet worden, bijvoorbeeld formulierdata of een dure real-time data stream. Onderstaande video demonstreert het probleem.
Dit probleem is eenvoudig op te lossen door de instantievelden die gebonden zijn aan het formulier te resetten in de ionViewWillEnter methode.
export class NewChannelPage implements OnInit {
newChannelName = ''
isPublic = true
users: Promise<IProfile[]> = this.#dbService.retrieveMatchingUsers('')
addedUsers = new Set<string>()
userName = ''
// Niet relevante code weggelaten.
ionViewWillEnter() {
console.log('Entered new channel page, resetting view.')
this.newChannelName = ''
this.isPublic = true
this.users = new Promise((r) => r([]))
this.addedUsers = new Set<string>
this.userName = ''
}
}Door deze code toe te voegen is het probleem opgelost. Merk op dat in de console geprint wordt wanneer de lifecycle methode uitgevoerd wordt en dat dit duidelijk gebeurt als de view in beeld komt (in onderstaande video).
ionViewDidEnter
De methode ionViewDidEnter wordt uitgevoerd zodra de view zichtbaar is, en kan dus best gebruikt worden voor DOM-manipulatie of om elementen in de view aan te spreken via een @ViewChild() decorator. Deze methode is dan ook de eerste locatie waar een variabele die gebruik maakt van de @ViewChild() decorator niet langer undefined is.
ionViewWillLeave
Deze methode wordt uitgevoerd vlak voordat de view verdwijnt en de volgende view getoond wordt. Dit is de perfecte locatie om scroll posities of andere, van de view afhankelijke data, uit te lezen en te bewaren.
ionViewDidLeave
Deze methode wordt uitgevoerd nadat de view niet langer zichtbaar is. Hier kan je subscriptions of andere asynchrone processen stoppen die niet mogen blijven draaien op de achtergrond.
Angular lifecycle
Angular bevat een groot aantal lifecycle methodes, het merendeel wordt niet besproken tijdens deze les omdat de Ionic methodes een betere integratie bieden met de Ionic Router en RouteReuseStrategy. Angular voorziet echter 2 belangrijke methodes ngOnInit en ngOnDestroy. Daarnaast bespreken we ook de ngAfterViewChecked methode.
ngOnInit
De nOnInit lifecycle methode, wordt uitgevoerd nadat de Angular component alle data-bound properties geïnitialiseerd heeft en nadat de constructor uitgevoerd is. Dit is, zoals al gezien in les 2, de ideale plaats om navigatieparameters op te halen, of iets te doen met attributen die gedefinieerd zijn met een @Input() decorator. Om gebruik te kunnen maken van deze lifecycle hook moet de component (pagina) de interface OnInit implementeren. Componenten en pagina's die aangemaakt zijn via het ionic generate commando implementeren deze interface automatisch.
import { Component, OnInit } from '@angular/core';
export class SomePage implements OnInit {
constructor() {
// Eerst uitgevoerd, eenmaal per instantie van een component.
}
ngOnInit(): void {
// Als tweede uitgevoerd, eenmaal per instantie van een component.
}
}ngOnDestroy
De ngOnDestroy lifecycle hook wordt uitgevoerd nadat de view uit de DOM, en de pagina uit het navigation stack verwijderd is. Deze methode kan gebruikt worden om eventuele subscriptions op observables te annuleren of om timers en intervals stop te zetten.
import { Component, OnInit, OnDestroy } from '@angular/core';
export class SomePage implements OnInit, OnDestroy {
constructor() {
// Eerst uitgevoerd, eenmaal per instantie van een component.
}
ngOnInit(): void {
// Als tweede uitgevoerd, eenmaal per instantie van een component.
}
ngOnDestroy(): void {
// Als laatste uitgevoerd, eenmaal per instantie van een component.
}
}Om deze lifecycle methode te gebruiken, moet de OnDestroy interface geïmplementeerd worden. We passen de code van de ChannelPage component uit met een ngOnDestroy methode die, als de component verwijderd wordt, een boodschap uitprint naar de console.
export class ChannelPage implements OnInit, OnDestroy {
// Niet relevante code weggelaten.
channelName = 'General'
ngOnInit() {
console.log(`${this.channelName} page initialized`);
this.channelName = this.#activatedRoute.snapshot.paramMap.get('channelName') ?? 'General'
this.messagesObservable = this.#dbService.retrieveMessages(this.channelName)
}
ngOnDestroy(): void {
console.log(`${this.channelName} page destroyed`);
}
}Let op, het is mogelijk dat een component herbruikt wordt door Ionic, dit betekent dat de ngOnDestroy methode niet noodzakelijk uitgevoerd wordt als de component uit de view verdwijnt, maar pas als de component effectief verwijderd wordt uit de navigation stack. Dit gebeurt als onder volgende voorwaarden:
- Als de gebruiker de "back" knop indrukt (Android/Browser) of het "back-gesture" uitvoert op (iOS/Android), zal de huidige pagina van het navigation stack verwijderd worden.
- Als de gebruiker van pagina A naar B naar C naar A navigeert, dan zullen pagina's B en C verwijderd worden. Ionic doorzoekt het navigation stack naar een bestaande instantie van de pagina, vervolgens worden alle elementen uit het stack verwijderd totdat de gewenste component (A) gevonden wordt. In onderstaand voorbeeld is A = General, B = Mobile Apps en C = React.
ngAfterViewChecked
De ngAfterViewChecked lifecycle hook wordt uitgevoerd nadat Angular gecontroleerd heeft of er in de view iets moet aangepast worden (change detection) en de nodige wijzigen gerenderd zijn. Om deze methode te gebruiken moet de AfterViewChecked interface geïmplementeerd worden.

Bron: https://ionicframework.com/docs/angular/lifecycle
Scroll-positie aanpassen via lifecycle methodes
Momenteel wordt de scroll-positie gereset als de gebruiker een nieuwe boodschap verstuurd, de eerste boodschap staat terug in de viewport, ook al was de gebruiker verder naar onder gescrold voordat de boodschap verstuurd werd. Onderstaande video demonstreert dit probleem.
Via de ngAfterViewChecked hook kunnen we dit probleem oplossen, eerst passen we de ionViewDidEnter methode aan zodat deze het element dat de scroll-positie bevat ophaalt en bewaard als instantievariabele.
Via de @ViewChild() decorator kunnen we de <ion-content> component aanspreken vanuit de TypeScript code. Deze component neemt een component als argument en haalt het eerste voorkomen van deze component in de template op. De IonContent component bevat een property getScrollElement() die het HTMLElement teruggeeft dat de effectieve scroll-positie bevat. Let op, deze code moet in de ionViewDidEnter methode staan omdat deze steunt op een volledig geïnstantieerde view.
import {IonContent} from '@ionic/angular'
export class ChannelPage implements OnInit, AfterViewChecked, OnDestroy {
// Niet relevant code weggelaten.
@ViewChild(IonContent) content?: IonContent
#scrollElement?: HTMLElement
ionViewDidEnter(): void {
this.content?.getScrollElement()
.then(x => this.#scrollElement = x)
}
}Vervolgens passen we de sendMessage methode aan zodat deze de verticale scroll-positie ophaalt voordat een bericht verstuurd wordt en deze data in een instantievariabele bewaard. Let op, deze caching in een instantievariabele is noodzakelijk, de scroll-positie wordt namelijk aangepast na een re-render.
export class ChannelPage implements OnInit, AfterViewChecked, OnDestroy {
// Niet relevante code weggelaten.
@ViewChild(IonContent) content?: IonContent
#scrollElement?: HTMLElement
#scrollTop?: number
sendMessage(): void {
this.#scrollTop = this.#scrollElement?.scrollTop
if (this.newMessage) {
this.#dbService.sendMessage(this.channelName, this.newMessage).then()
this.newMessage = undefined
}
}
}Tenslotte kunnen we de ngAfterViewChecked hook gebruiken om de scroll-positie terug correct in te stellen. We gebruiken hiervoor de scrollToPoint methode, als eerste parameter geven we 0 door omdat onze applicatie geen ondersteuning biedt voor horizontaal scrollen en de horizontale scroll-positie dus steeds 0 is. Als tweede parameter geven we de verticale scroll-positie mee.
Na een time-out van 500 milliseconden scrollen we dan naar beneden zodat de nieuw boodschap zichtbaar wordt.
export class ChannelPage implements OnInit, AfterViewChecked, OnDestroy {
// Niet relevante code weggelaten.
#scrollTop: number
ngAfterViewChecked(): void {
if (this.content && this.#scrollElement && this.#scrollTop) {
this.content
.scrollToPoint(0, this.#scrollTop)
.then(_ => {
this.#scrollTop = undefined
setTimeout(() => this.content?.scrollToBottom(500), 500)
})
}
}
}Als we nu een nieuw bericht versturen, zien we dat de scroll-positie onveranderd blijft en dat er automatisch naar beneden gescrold wordt.