Les 1: TypeScript
Les 1: TypeScript
Als oefening op TypeScript bouwen we een enkele eenvoudige formulieren en het spel Tic-Tac-Toe. We gebruiken Ionic nog niet, dan hebben we namelijk ook Angular kennis nodig.
Al wat we in deze oefeningen manueel doen, i.e. het installeren van TypeScript, een linter, het compileren van TypeScript naar ES5, het configureren van de TypeScript compiler en de linter, wordt later door de Ionic CLI gedaan. Het is echter interessant om te zien hoe dit werkt en om te weten waarvoor elk bestand dient.
De interactie met de DOM (document.getElementById) wordt in een Ionic/Angular project voor het grootste deel door de frameworks afgehandeld, deze les focussen we op TypeScript en moeten we deze interactie nog wel zelf coderen.
In deze oefeningenreeks oefen je op:
- TypeScript
- Het lezen van documentatie
- Het lezen van foutmeldingen
- Het aanpassen van een default ESLint of TypeScript configuratie
Project aanmaken & configureren
Voor je aan deze opgave begint moet de developer omgeving geïnstalleerd zijn.
Navigeer naar de map waar je je project wil aanmaken (hieronder en in de rest van de cursus wordt "C:\Projects" gebruikt) en maak een map aan voor je project.
Open deze (lege) map vervolgens met je favoriete IDE (WebStorm is aangeraden en wordt in volgende voorbeelden gebruikt).
Open vervolgens de terminal, je vindt deze linksonder.

Via het onderstaande commando kan je een nieuw npm project initialiseren. Het commando maakt hiervoor het bestand package.json aan. Dit bestand bevat informatie over de dependencies, de auteur, de licentie, ... Maak een nieuw project aan en pas minstens de naam van de auteur en de naam van het project aan.
pnpm init
Installeer vervolgens TypeScript en ESLint in je project, de eerste module is de taal TypeScript. De tweede module is een pakket dat controleert of er al dan niet stijlfouten in je code zitten, de gegenereerde foutmeldingen en waarschuwingen komen bovenop diegene die gegenereerd worden door je IDE.
pnpm add --save-dev typescript
pnpm add -D eslint
Merk op dat typescript en ESLint allebei toegevoegd zijn aan de dev-dependencies. De vlag -D is de korte vorm van de parameter --save-dev. Als je deze vlag niet toevoegt, zullen de libraries toegevoegd worden als een runtime dependency, wat betekent dat deze code aanwezig zal zijn in een productiebuild, m.a.w. dat deze code bij in je uiteindelijke website/app zit. Hierdoor zal je website/app groter worden en het downloaden/installeren trager. Je kan controleren of de bibliotheken effectief bewaard zijn in het bestand package.json. Dit bestand zou er ongeveer als volgt moeten uitzien (de versienummers kunnen verschillen).
{
"name": "mobile_apps_lecture1_exercise",
"version": "1.0.0",
"description": "The first exercise for the course Mobile Applications thought at the Thomas More Technical College.",
"main": "index.js",
"scripts": {
"test": "echo \"Error: no test specified\" && exit 1"
},
"keywords": [],
"author": "Sebastiaan Henau",
"license": "MIT",
"devDependencies": {
"eslint": "^8.23.1",
"typescript": "^4.8.3"
}
}
ESLint
De twee geïnstalleerde modules moeten nog geconfigureerd worden. ESLint vergt weinig configuratie, je hoeft enkel onderstaand commando uit te voeren.
pnpm exec eslint --init
Vervolgens krijg je een aantal vragen te zien in de console, kies hier opeenvolgend voor:
- To check syntax, find problems, and enforce code style
- JavaScript modules (import/export)
- None of these
- Yes
- Browser
- Use a popular style guide
- Standard
- JavaScript
- Yes
- pnpm
ESLint verwacht, volgens de standaard configuratie, documentatie voor elke functie. Wil je dit afzetten, dan kan je in .eslintrc.js, in het object rules de optie, require-jsdoc afzetten. Een lijn code mag standaard ook maar 89 karakters breed zijn, in Ionic ligt de limiet hoger, dus ook deze regel mag je eventueel afzetten.
rules: {
'max-len': 0,
'require-jsdoc': 0
}
TypeScript
Om TypeScript te configureren is er iets meer werk nodig. Je begint echter opnieuw met een eenvoudig commando.
pnpm exec tsc --init
Open vervolgens het bestand tsconfig.json en doe onderstaande aanpassingen.
| Sleutel | Waarde | Verklaring |
|---|---|---|
| module | "ESNext" | De manier waarop modules ondersteund worden. ESNext zorgt er onder anderen voor dat de import en export statements werken. De andere opties kunnen gebruikt worden om de uitvoer te bundelen tot één file, maar dit vereist extra bibliotheken. |
| lib | ["ESNext", "DOM"] | Zorgt dat je de volledige functionaliteit van JavaScript kan gebruiken. Ook mogelijk voor de nog niet definitieve versies ("ESNext"). Alhoewel "es2021" eventueel als fout aangeduid wordt is dit wel correct. De schema controle voor tsconfig.json is nog niet geüpdatet maar TypeScript zelf bied hier wel ondersteuning voor. De waarde "dom" zorgt er voor dat je toegang hebt tot de DOM en dus HTML elementen kan aanspreken. |
| outDir | "./dist" | De folder naar waar de gecompileerde TypeScript bestanden gekopieerd worden. |
| noImplicitAny | true | Als deze optie op true gezet wordt, zal TypeScript errors geven voor inferred any variabelen. Als een variabele echt het any type nodig heeft, dan moet je dit uitdrukkelijk vermelden. |
| rootDir | "./src" | Deze optie verteld TypeScript waar de broncode gevonden kan worden, we zullen verder in de oefening al onze TypeScript code in de src map plaatsen. |
TypeScript en ESLint
Maak een nieuw TypeScript bestand, /src/test.ts, aan in WebStorm. Definieer hierin volgende functie:
function test() {
return 'This works'
}
Als alles correct geconfigureerd is, geeft WebStrom normaalgezien onderstaande foutmelding als je het nieuwe bestand open hebt staan.
Deze error is te wijten aan het feit dat ESLint niet weet waar de TypeScript configuratie te vinden is. Deze config bepaald de regels in verband met types en wat hiermee wel of niet toegestaan is. Je kan dit probleem oplossen door in .eslintrc.js, in de parserOptions de property project in te stellen op ['./tsconfig.json'].
Als je terug naar het testbestand gaat, zou je nu een foutmelding moeten zien die aangeeft dat de functie een return type mist, voeg dit toe en controleer of de foutmelding verdwijnt. Vervolgens krijg je nog een foutmelding over het aantal spaties, ESLint gebruikt standaard spaties indentatie, terwijl de code die je hierboven gekopieerd hebt er gebruikt. Ga in de ESLint documentatie op zoek de juiste manier om de configuratie aan te passen zodat spaties vereist zijn.
Let op, je moet de configuratie niet enkel aanpassen voor de indent regel maar ook voor de @typescript-eslint/indent regel. Dit komt omdat de TypeScript plug-in voor ESLint de standaard opties uitbreid met extra opties voor TypeScript syntax. De indent check is spijtig genoeg buggy voor TypeScript en zal zo blijven. Je kan onderstaande regel toevoegen om de problemen op te lossen ten kosten van wat error checks.
'@typescript-eslint/indent': [
'error',
4,
{
ignoredNodes: [
'PropertyDefinition[decorators]',
'TSUnionType',
'TSTypeParameterInstantiation',
'TSIntersectionType'
]
}
]
Je hebt tijdens het maken van voorgaande oefening misschien gemerkt dat de standaard configuratie van TypeScript plug-in voor ESLint vereist dat je spaties voor de parameters van een functie moet plaatsen. Aangezien dit tegen de ingebouwde formatter van WebStorm ingaat, en aangezien dit in een Ionic project geen probleem is, kan je, als je dat wil, onderstaande regel toevoegen. Er bestaat een soortgelijke regel voor inline-objecten ({prop: value, prop2: value}), ook deze kan je deactiveren.
'@typescript-eslint/space-before-function-paren': 0,
'@typescript-eslint/object-curly-spacing': 0
Startbestanden
Download de startbestanden. Dit repository bevat 2 mappen, dist en src. De eerste map bevat de HTML en CSS bestanden (die niet aangepast moeten worden), de tweede map bevat de TypeScript bestanden (die nog ingevuld moeten worden). Plaats deze startbestanden in de root van de map die je aangemaakt hebt voor deze oefeningen, dus op hetzelfde niveau als de node_modules map.
Om je project te testen open je het bestand dist/index.html, vervolgens kan je (in WebStorm) de shortcut alt + F2 gebruiken om een browser te selecteren waarin de pagina geopend moet worden. Je vindt rechtsboven ook een menu waar de verschillende browsers geselecteerd kunnen worden (WebStorm bevat ook een ingebouwde preview.)

Compileren van een TypeScript programma
TypeScript is ideaal voor een ontwikkelaar, maar browsers ondersteunen TypeScript niet. Dus moet de TypeScript code getranspileerd worden naar JavaScript code die begrijpbaar is voor de browser. Dit kan heel eenvoudig, je hoeft enkel onderstaand commando in te typen.
pnpm exec tsc
Als alles correct geconfigureerd is staan er nu zes bestanden (exercise1.js, exercise2.js, exercise3.js, exercise4.js, gameBoard.js en move.js) in de dist map (en eventueel test.js als je dit bestand nog niet verwijderd hebt). Deze bestanden zijn natuurlijk nog leeg. Telkens je een wijziging gemaakt hebt in de TypeScript code en deze wil uittesten, moet je de code opnieuw transpileren en het commando dus opnieuw uitvoeren.
Oefening 1: Product bereken
Voor oefening 1 wordt gevraagd om een getal in te lezen uit 2 input elementen, het product hiervan te berekenen (als op de knop gedrukt wordt) en dit product uit te schrijven in een <ul>.
Er zijn enkele voorwaarden verbonden aan deze oefening:
- Er mogen zich geen linting fouten voordoen.
- Elke variabele en functie moet de juiste types krijgen, het type any mag niet gebruikt worden.
- Definieer een functie calculate die twee getallen als parameter krijg en het product berekent.
- De verschillende berekende uitkomsten worden in een array van strings bewaard.
- Je voorziet twee manieren om de elementen in deze array uit te printen, via een klassieke for lus en via de .foreach methode die beschikbaar is voor arrays.
- Je definieert twee globale variabelen number1 en number2 die steeds dezelfde waarde bevatten als het formulier op de webpagina (zo werkt Angular ook).
Tips
Het is voor deze oefeningen interessant om te weten dat de getDocumentById een HTMLElement teruggeeft en dat de interface HTMLInputElement hiervan overerft. Je mag get resultaat van de getDocumentById functie casten naar een een HTMLInputElement.
BEKIJK ENKEL ALS JE VAST ZIT: Oplossingen
Onderstaande oplossingen werken, op de linting errors na. Als je de code niet zelf geschreven krijgt, kan je onderstaande code kopiëren en de linting errors oplossen.
const number1Input = document.getElementById('number1') as HTMLInputElement
const number2Input = document.getElementById('number2') as HTMLInputElement
const calculateButton = document.getElementById('calc-button')
const resultList = document.getElementById('result-list')
// Attach on change handlers so the variables number1 & number2 always contain the latest value.
let number1: number
let number2: number
number1Input.addEventListener('change',
(evt) => {
number1 = Number((evt.target as HTMLInputElement)?.value)
})
number2Input.addEventListener('change',
(evt) => {
number2 = Number((evt.target as HTMLInputElement)?.value)
})
// The list of all the calculated values.
const calculations: string[] = []
function calculate(number1: number, number2: number): number {
return number1 * number2
}
calculateButton?.addEventListener('click', handleClick)
function handleClick(): void {
const result = calculate(number1, number2)
calculations.push(`${number1} * ${number2} = ${result}`)
addCalculationsToDom()
}
function addCalculationsToDom(): void {
if (resultList === null) return
resultList.innerHTML = ''
// for (const calc of calculations) {
// resultList.innerHTML += `<li>${calc}</li>`;
// }
// Alternative method using arrow functions.
calculations.forEach((calc) => {
resultList.innerHTML += `<li>${calc}</li>`
})
}
Oefening 2: Product bereken v2
Op functioneel vlak, is er geen verschil tussen oefening 1 & 2. De opgave wordt echter uitgebreid met een klasse Exercise2. Deze klasse bevat als instantievariabelen number1, number2 en calculations (de lijst met alle berekende producten). Al deze instantievariabelen zijn private voorzie dus de nodige getters en setters waarmee de variabelen ingesteld kunnen worden of uitgelezen kunnen worden.
Verder gelden volgende voorwaarden:
- Er mogen zich geen linting fouten voordoen.
- Elke variabele en functie moet de juiste types krijgen, het type any mag niet gebruikt worden.
Oefening 3: Vakken toevoegen
Schrijf een programma waarmee vakken (uit de graduaatsopleiding programmeren) toegevoegd en uitgeprint kunnen worden. Hieronder zie je de het beoogde resultaat.
Er gelden opnieuw enkele voorwaarden:
- Er mogen zich geen linting fouten voordoen.
- Elke variabele en functie moet de juiste types krijgen, het type any mag niet gebruikt worden.
- De mogelijke categorieën (leerlijnen) worden gedefinieerd in een enum en de select wordt vanuit de code opgebouwd. Hiervoor kan
Object.values(enum)nuttig zijn, dit vormt een enum om naar een array, zo kan er geïtereerd worden over het enum. Dit enum bestaat uit mogelijke waarden (naam zelf te kiezen), de waarden in het enum krijgen onderstaande tekstwaarden (in de plaats van de standaard , , $2, ).

- Je maakt een interface Subject aan die de informatie over één vak bevat en waar de leerlijn eventueel leeg mag zijn. Het jaar en semester mogen enkel 1 of 2 zijn, niets anders.
- De verschillende vakken worden in een array van Subjects bewaard.
Oefening 4: Tic-Tac-Toe
We spreken af dat de TypeScript code voor dit vak volledig strongly typed is, dit betekent dat elke variabele en parameter een datatype moet krijgen, daarbovenop moet elke functie een return type krijgen. Voor de rest van de cursus is het toegestaan om inferred types te gebruiken, maar omdat deze les de eerste kennismaking is met TypeScript moet je alle variabelen ook een type geven als dit duidelijk is uit de toegekende waarde.
Probeer eerst zelf je code te schrijven, de .ts bestanden bevatten beschrijvingen van de nodige inhoud. Als je vast zit of niet weet hoe je ergens aan kan beginnen kan de API beschrijving (die ook in de oplossingen gebruikt wordt) je misschien verder helpen.
Gebruik voor de interactie met de DOM vanilla JavaScript, dus via de document.getElementByX() methodes. jQuery en soortgelijke bibliotheken zijn niet toegestaan. Voor deze opgave is de HTML code reeds voorzien in de startbestanden.
move.ts
Dit bestand definieert een enum met de beschikbare zetten, namelijk X en O. Daarnaast moet het enum geïmporteerd kunnen worden in andere bestanden.
gameBoard.ts
Dit bestand bevat een klasse GameBoard waarin de status van het spel bijgehouden wordt. Voeg volgende eigenschappen toe en zorg dat deze geïnitialiseerd worden met passende waarden.
- gameState: een 1-dimensionale arrayrepresentatie van het spelbord. Dit is een array die een combinatie van undefined en Move objecten bevat.
- currentMove: de speler die aan beurt is
- movesPlayed: het aantal zetten die reeds gedaan zijn.
De klasse moet ook volgende methodes bevatten:
API
- setMove: neemt een index als argument en past de gameState op deze index aan naar de huidige speler (als er op deze plaats nog geen zet gedaan is). Als de zet geldig was worden de nodige variabelen aangepast.
- getBoard: een getter voor de gameState.
- getMovesPlayed: een getter voor het aantal zetten dat reeds gebeurd is.
- calculateWinner: een functie die bepaald of er reeds een winnaar is.
exercise4.ts
Het ingangspunt van deze opgave. Dit bestand moet de event-handlers bouwen en koppelen aan de vakjes op het spelbord. Daarnaast moet het bord opgevuld worden en de winnende speler getoond worden.