Les 7: Varia

Sebastiaan HenauOngeveer 13 minuten

Les 7: Varia

Tijdens deze laatste les bekijken we hoe we een applicatie, via Firebase, kunnen publiceren als progressive web app. Daarnaast bekijken we welke Ionic en Angular lifecycle methodes er bestaan en hoe deze gebruikt kunnen worden.

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 workeropen in new window aangemaakt worden. Ten tweede moet een geldig web App Manifestopen in new window 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.

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

  2. 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, zal een service worker er ook voor zorgen 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 manifestopen in new window 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 14.12.2 staat, dan gebruik je in het ng add commando het versienummer ^14.0.0.

ng add @angular/pwa@^MAJOR_ANGULAR_VERSION_NUMBER.0.0

Dit 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 6.

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

Figuur 1: Angular Service Worker Configuration

Dit bestand beschrijft hoe de service worker werkt, dus moet deze configuratie in de JavaScript app ingeladen worden. Dit gebeurt (automatisch via ng add) in app.module.ts.

import { ServiceWorkerModule } from '@angular/service-worker';

@NgModule({
  declarations: [AppComponent, MenuChannelGroupComponent],
  entryComponents: [],
  imports: [BrowserModule, IonicModule.forRoot(), AppRoutingModule,
    provideFirebaseApp(() => initializeApp(environment.firebaseConfig)),
    provideAuth(() => getAuth()),
    provideFirestore(() => {
      const firestore = getFirestore();
      enableMultiTabIndexedDbPersistence(firestore);
      return firestore;
    }),
    ServiceWorkerModule.register('ngsw-worker.js', {
      enabled: environment.production,
      // Register the ServiceWorker as soon as the app 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.

Figuur 2: Web app manifest

De standaard inhoud van dit bestand moet, zoals hieronder te zien, duidelijk aangepast worden. Daarnaast zien we ook dat het manifest automatisch gelinkt is op de index.html pagina.

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"
    },
    ... 
  ]
}

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.

Figuur 3: Theme color toegepast in een Android browser

Bron: developers.google.comopen in new window

background_color

Dit attribuut wordt gebruikt om de achtergrondkleur van de applicatie in te stellen terwijl de stylesheets aan het laden zijn. Dit 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 weergegeven te worden, 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).

Figuur 4: Installatie van een PWA op Android

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 issueopen in new window 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 zal de optie standalone de app natuurlijk ook openen 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.

Figuur 5: Standalone installatie van een PWA op Windows

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.

Figuur 6: Minimal-ui installatie van een PWA op Windows

scope

Via dit attribuut kunnen we de paden die bezocht mogen worden als de app als PWA uitgevoerd wordt 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 dpiopen in new window van het toestel, het beste icoon uit gekozen wordt. Minimaal is een icoon van 192x192 en van 512x512 nodig. Op basis van deze resoluties voert een browser eventueel conversies uit om een gepast icoon te genereren. Via de tool pwa-assets-generatoropen in new window kunnen we uit hetzelfde icoon als voor de Android app, de nodige files genereren.

pnpm add -g pwa-asset-generator

Het onderstaand commando genereert de nodige icoontjes, en voegt deze automatisch toe aan het manifest en de index. 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": "mobile_lecture_7_example",
  "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.

pnpm add -g firebase-tools

De eerste keer dat je deze tool gebruikt, moet je inloggen op Firebase. Dit kan via het commando

firebase login

Vervolgens kan deze tool gebruikt worden om te verbinden met een Firebase project. Het commando

firebase init

produceert onderstaande uitvoer:

PS 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 directory

Na 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 products

Vervolgens 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 6), 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 project

Vervolgens wordt je gevraagd naar de public directory. 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) www






 

Vervolgens wordt je gevraagd of je de hosting wil 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. Het verzoek zal 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) y

Vervolgens 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) N

Tenslotte krijg je eventueel de vraag om een bestaande www/index.html 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 en naar Firebase deployen. Om zeker te zijn dat dit de laatste versie is, maak je best nog een nieuwe build aan.

ionic build --prod
firebase deploy

Het 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.app

De 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, deze kunnen gebruikt worden om actief 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 en er zich geen errors voordoen.

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 bevatten, zoals ingevulde formuliervelden die eigenlijk leeg zouden moeten zijn.

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.

Figuur 7: Problemen door het herbruiken van een component

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: Profile[] = [];
  addedUsers = {};
  userName = '';

  // Niet relevante code weggelaten.
    
  ionViewWillEnter() {
    console.log('Entered new channel page, resetting view.');
    this.newChannelName = '';
    this.isPublic = true;
    this.users = of([]);
    this.addedUsers = {};
    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.

Figuur 8: Een leeg formulier, dankzij de ionViewWillEnter lifecycle methode

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, zoals al gezien in les 3, 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 kunnen 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.
  
  this.channelName = '';

  ngOnInit(): void {
    console.log(`${this.channelName} page initialized`);
  }

  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, zal de huidige pagina van het navigation stack verwijderd worden.
Figuur 9: ngOnDestroy & het navigation stack, back button
  • Als de gebruiker van pagina A naar B naar C naar A navigeert, dan zullen pagina's B en C verwijderd worden. Ionic zal het navigation stack doorzoeken naar een bestaande instantie van de pagina, vervolgens worden alle elementen uit het stack totdat de gewenste component (A) gevonden wordt. In onderstaand voorbeeld is A = General, B = Mobile Apps en C = React.
Figuur 10: Pagina's van het navigation stack verwijderen door terug te gaan naar een vorige pagina

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.

Figuur 11: Ionic & Angular Lifecycle

Bron: ionicframework.comopen in new window

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.

Figuur 12: De positie van de scrollbar wordt gereset

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 in 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 = 0;

   async sendMessage(): Promise<void> {
      this.scrollTop = this.scrollElement?.scrollTop;
      await this.dbService.sendMessage(this.channelName, this.newMessage);
      this.newMessage = undefined;
   }
}



 


 





Tenslotte kunnen we de ngAfterViewChecked hook gebruiken om de scroll-positie terug correct in te stellen. We gebruiken hier de scrollToPoint methode met als eerste parameter 0 omdat onze applicatie geen ondersteuning bied voor horizontaal scrollen en de horizontale scroll-positie dus steeds 0 is.

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 = 0;
  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.

Figuur 13: De scroll-positie blijft onveranderd en na een timeout wordt er automatisch naar beneden gescrold.

Voorbeeldcode

Volledig uitgewerkte lesvoorbeelden met commentaaropen in new window

Laatst geüpdate:
Bijdragers: Sebastiaan Henau