Avez-vous une question? (216) 97 656 803 hajjriadh@gmail.com
Angular

TP TypeScript : gestion des inscriptions d’un centre de formation

Un TP complet pour maîtriser TypeScript avant Angular. Vous créez pas à pas un programme de gestion des inscriptions : modèles typés, classes, paiements avec une classe abstraite, dépôt générique, ser...

0 chapitres 0 QCM Accès gratuit
Dans cet article

À propos de ce cours

Un TP complet pour maîtriser TypeScript avant Angular. Vous créez pas à pas un programme de gestion des inscriptions : modèles typés, classes, paiements avec une classe abstraite, dépôt générique, service et décorateurs, avec un commit à chaque étape.

TP TypeScript : gestion des inscriptions du centre FormaPlus

  1. Objectifs

  • Ce TP de 6 heures clôt le chapitre Programmer en TypeScript. En le réalisant, vous allez apprendre à :
    • créer et configurer un projet TypeScript avec Node.js : tsconfig.json, mode strict, scripts npm, compilation avec tsc;
    • utiliser les types de base, l'inférence, les tableaux, les tuples, les unions et les types littéraux, et comprendre la différence entre any et unknown;
    • écrire des fonctions typées: paramètres facultatifs et par défaut, type de retour, rétrécissement avec typeof;
    • décrire les données de l'application avec des interfaces et des alias de type, et les organiser en modules;
    • écrire des classes: propriétés de paramètre, private, readonly, static, accesseurs ;
    • utiliser l'héritage, une classe abstraite, override et implements;
    • écrire une classe générique et utiliser les types utilitaires Omit et Partial;
    • gérer les résultats d'une opération avec une union discriminée Resultat<T>, et « programmer vers une interface » ;
    • écrire et appliquer des décorateurs de classe et de méthode ;
    • lire les messages d'erreur du compilateur et les corriger ;
    • versionner son travail avec Git et le publier sur GitHub dès le départ : dépôt, commit à chaque partie, push, README.md et version finale étiquetée.
  • Prérequis : avoir suivi les leçons du chapitre, en particulier Les classes en TypeScript et Les interfaces de classe en TypeScript, ainsi que les rappels ECMAScript (classes et modules, spread et rest).
  • Avant la séance :
    • Node.js en version LTS (vérifier avec node -v) et VS Code;
    • un compte GitHub gratuit et Git installé (vérifier avec git --version).
  • Version de TypeScript : ce TP a été réalisé avec TypeScript 7.0, installé par npm install -D typescript. Il fonctionne aussi avec TypeScript 6.
  1. Description

  • Le centre de formation FormaPlus veut un petit programme pour gérer les inscriptions à ses sessions de formation : enregistrer les stagiaires, les inscrire à une session en tenant compte des places, encaisser les paiements selon trois modes (espèces, carte, paiement en trois fois), prévenir les stagiaires et afficher un tableau de bord.
  • Avant de construire l'interface web avec Angular, on écrit et on teste toute cette logique métier en TypeScript, exécutée avec Node.js dans le terminal. C'est exactement le code qu'on retrouvera plus tard dans les modèles et les services d'une application Angular.
  • Le programme est organisé en modules :
Dossier Fichiers Contenu Notions TypeScript Partie
utils/ format.ts fonctions de mise en forme (montants, dates, mentions) fonctions typées, unions, typeof 02
models/ personne.ts, formation.ts, inscription.ts forme des données interfaces, extends, types littéraux, union discriminée 03
data/ donnees.ts jeu de données de départ tableaux typés 03
classes/ session.ts une session de formation et ses places classe, private, readonly, static, accesseurs 04
classes/ paiement.ts les trois modes de paiement classe abstraite, héritage, override, implements 05
classes/ depot.ts stockage générique des données génériques, Omit, Partial 06
services/ notificateur.ts, inscription.service.ts logique d'inscription et de paiement programmer vers une interface, switch exhaustif 07
decorateurs/ journal.ts journalisation et mesure du temps décorateurs de classe et de méthode 08
src/ main.ts programme principal et tableau de bord intégration de tout le reste 07 et 09
  • Résultats attendus : chaque partie se termine par une capture « Résultat attendu » : l'affichage du terminal que vous devez obtenir. Comparez votre écran avec cette capture avant de faire votre commit.
  • Fichier reponses.md: les questions marquées (R) demandent une réponse écrite, à rédiger au fil du TP dans le fichier reponses.md à la racine du projet.
  • Répartition du temps :
Partie Notions travaillées Durée
Partie 00 – Mise en place du projet TypeScript et du dépôt GitHub npm, tsconfig.json, scripts, Git 25 min
Partie 01 – Types de base, inférence et unions types primitifs, tableaux, tuples, unions, unknown 35 min
Partie 02 – Fonctions typées paramètres, retour, typeof, modules 30 min
Partie 03 – Modèles de données interfaces, extends, types littéraux 35 min
Partie 04 – La classe Session classe, private, readonly, static, get 40 min
Partie 05 – Les paiements : héritage et abstraction abstract, extends, override, implements 40 min
Partie 06 – Un dépôt générique <T>, contraintes, Omit, Partial 40 min
Partie 07 – Le service d'inscription union discriminée, interface, switch 40 min
Partie 08 – Les décorateurs décorateurs de méthode et de classe 30 min
Partie 09 – Programme principal et vérifications intégration, tsc --noEmit, source maps 25 min
Partie 10 – Finalisation et version 1.0 sur GitHub README.md, git tag 20 min
Total   6 h
  1. Travail à réaliser

  • Partie 00 Mise en place du projet TypeScript et du dépôt GitHub
  • Q0.1. Créer le dossier du projet, l'ouvrir dans VS Code et installer TypeScript dans le projet (dépendance de développement) :
mkdir gestion-formaplus
cd gestion-formaplus
npm init -y
npm install -D typescript
npx tsc -v
code .
  • Q0.2. Créer l'arborescence ci-dessous (à gauche) avec des fichiers vides, sauf src/main.ts qui contient pour l'instant une seule ligne : console.log('FormaPlus – projet TypeScript prêt');. Créer aussi le fichier .gitignore (à droite).
gestion-formaplus/
├── .gitignore
├── package.json
├── tsconfig.json
├── README.md            (Partie 10)
├── reponses.md          (au fil du TP)
├── captures/            (au fil du TP)
└── src/
    ├── main.ts
    ├── utils/format.ts
    ├── models/
    │   ├── personne.ts
    │   ├── formation.ts
    │   └── inscription.ts
    ├── data/donnees.ts
    ├── classes/
    │   ├── session.ts
    │   ├── paiement.ts
    │   └── depot.ts
    ├── services/
    │   ├── notificateur.ts
    │   └── inscription.service.ts
    ├── decorateurs/journal.ts
    └── exercices/       (fichiers de test p01 à p08)
# .gitignore
node_modules/
dist/

# Fichiers système
.DS_Store
Thumbs.db
  • Q0.3. Créer le fichier tsconfig.json suivant (à gauche), puis modifier package.json: ajouter "type": "module" et remplacer la section scripts (à droite). (R) À quoi servent les options strict, outDir et rootDir? Que fait chacun des quatre scripts ?
{
  "compilerOptions": {
    "target": "ES2023",
    "module": "NodeNext",
    "moduleResolution": "NodeNext",
    "strict": true,
    "noImplicitOverride": true,
    "noImplicitReturns": true,
    "outDir": "dist",
    "rootDir": "src",
    "sourceMap": true
  },
  "include": ["src"]
}
{
  "name": "gestion-formaplus",
  "version": "1.0.0",
  "type": "module",
  "scripts": {
    "build": "tsc",
    "start": "node dist/main.js",
    "dev": "tsc --watch",
    "verifier": "tsc --noEmit"
  },
  "devDependencies": {
    "typescript": "^7.0.2"
  }
}
Les imports se terminent par .js: avec "module": "NodeNext", on écrit import { calculerMoyenne } from '../utils/format.js'; même si le fichier source s'appelle format.ts. TypeScript ne modifie pas les chemins d'import : c'est le fichier .js compilé que Node.js chargera. Dans Angular, on omet l'extension, car c'est l'outil de compilation d'Angular qui retrouve les fichiers.
  • Q0.4. Compiler puis exécuter : npm run build, puis npm start. (R) Quel dossier est apparu ? Ouvrir dist/main.js: en quoi diffère-t-il de src/main.ts? Que contient dist/main.js.map?
  • Q0.5. Créer reponses.md (titre « TP TypeScript – Réponses » et votre nom) et le dossier captures/ avec un fichier vide captures/.gitkeep.
  • Publier le projet sur GitHub dès le départ
  • Q0.6. Sur github.com/new, créer un dépôt nommé gestion-formaplus, sans README, .gitignore ni licence. Si le dépôt est privé, inviter l'enseignant comme collaborateur.
  • Q0.7. Configurer Git si ce n'est pas déjà fait, initialiser le dépôt local, faire le premier commit et l'envoyer :
# Identité (une seule fois par poste)
git config --global user.name "Prénom Nom"
git config --global user.email "adresse@exemple.com"

# Premier envoi du projet
git init
git add .
git commit -m "Partie 00 : création du projet TypeScript FormaPlus"
git branch -M main
git remote add origin https://github.com/VOTRE-COMPTE/gestion-formaplus.git
git push -u origin main
# Vérifications utiles
git status          # fichiers modifiés
git log --oneline   # historique des commits
git remote -v       # adresse du dépôt GitHub
  • Q0.8. (R) Sur GitHub, vérifier que node_modules/ et dist/ n'apparaissent pas. Pourquoi ne versionne-t-on ni l'un ni l'autre ? Comment un camarade qui clone le dépôt obtiendra-t-il TypeScript et le dossier dist/? Envoyer le lien du dépôt à l'enseignant avant de commencer la partie 01.
Authentification : lors du premier git push, GitHub n'accepte plus le mot de passe du compte. Connectez-vous par le navigateur quand Git le propose (Git Credential Manager ou VS Code), ou utilisez un Personal Access Token à la place du mot de passe. Un mot de passe ou un jeton ne doit jamais être écrit dans le code ni dans un fichier envoyé sur GitHub.
Règle du TP : un commit et un push à la fin de chaque partie.
Chaque partie se termine par un encadré « Git » qui donne le message de commit attendu. L'historique des commits fait partie de la note : un dépôt qui ne contient qu'un seul commit déposé en fin de séance n'obtient aucun point sur le critère Git.
Question de réflexion (R) : le compilateur vous signale une erreur que vous ne comprenez pas après la partie 05. Comment l'historique des commits vous aide-t-il à retrouver ce qui a changé depuis la dernière version qui compilait ?
  • Résultat attendu à la fin de la partie 00
Résultat attendu à la fin de la partie 00 du TP TypeScript
TypeScript installé dans le projet, compilation et exécution du premier fichier, premier commit envoyé sur GitHub. Vos numéros de version peuvent être différents.
  • Partie 01 Types de base, inférence et unions
  • Objectif : manipuler les types de TypeScript et observer ce que le compilateur vérifie.
  • Q1.1. Créer src/exercices/p01-types.ts avec le code ci-dessous, compiler avec npx tsc puis exécuter node dist/exercices/p01-types.js.
// Partie 01 – Types de base et inférence
const centre: string = 'FormaPlus';
const anneeOuverture: number = 2010;
let ouvert: boolean = true;
let effectif = 120; // type déduit : number

const modules: string[] = ['HTML', 'CSS', 'TypeScript'];
const notes: Array<number> = [12, 15.5, 9];
const coordonnees: [string, number] = ['Tunis', 1002]; // tuple

let identifiant: string | number = 'STG-001'; // union
identifiant = 42;

type Niveau = 'Débutant' | 'Intermédiaire' | 'Avancé'; // types littéraux
const niveau: Niveau = 'Intermédiaire';

let donneeInconnue: unknown = JSON.parse('{"nom":"Amira"}');
if (typeof donneeInconnue === 'object' && donneeInconnue !== null && 'nom' in donneeInconnue) {
  console.log('Nom reçu :', (donneeInconnue as { nom: string }).nom);
}

console.log(`${centre} (ouvert en ${anneeOuverture}) : ${effectif} stagiaires, ouvert = ${ouvert}`);
console.log(`Modules : ${modules.join(', ')} – moyenne des notes : ${(notes.reduce((s, n) => s + n, 0) / notes.length).toFixed(2)}`);
console.log(`Ville : ${coordonnees[0]}, code postal : ${coordonnees[1]}`);
console.log(`Identifiant : ${identifiant} (${typeof identifiant}) – niveau : ${niveau}`);
ouvert = false;
effectif += 5;
  • Q1.2. (R) Survoler chaque variable dans VS Code : quel type TypeScript a-t-il déduit pour effectif? Et pour niveau s'il était déclaré const niveau = 'Intermédiaire'; sans annotation ? Expliquer la différence.
  • Q1.3. Ajouter une par une les lignes du tableau ci-dessous à la fin du fichier, compiler, et (R) noter le message obtenu. Supprimer ou mettre en commentaire chaque ligne avant la suivante.
Ligne ajoutée Message attendu
effectif = 'cent vingt'; TS2322: Type 'string' is not assignable to type 'number'.
const niveau2: Niveau = 'Expert'; TS2322: Type '"Expert"' is not assignable to type 'Niveau'.
const lieu: [string, number] = [1002, 'Tunis']; deux erreurs TS2322, à relever
let u: unknown = 'abc'; u.toUpperCase(); TS18046: 'u' is of type 'unknown'.
let x: any = 5; x.toUpperCase(); aucune erreur à la compilation : exécuter le programme et relever l'erreur obtenue
  • Q1.4. (R) D'après la dernière ligne du tableau, pourquoi faut-il éviter any? Dans quel cas utilise-t-on unknown à la place ? Que fait le bloc if placé avant l'affichage du nom reçu ?
  • Q1.5. Ajouter un type Groupe qui n'accepte que 'DEV1', 'DEV2' et 'RES1', une variable de ce type, et l'afficher.
  • Résultat attendu à la fin de la partie 01
Résultat attendu à la fin de la partie 01 du TP TypeScript
Exécution du fichier de types, puis deux erreurs de compilation provoquées volontairement en Q1.3.
Git – fin de la partie 01 :
git add .
git commit -m "Partie 01 : types de base, unions et inférence"
git push
  • Partie 02 Fonctions typées
  • Objectif : écrire des fonctions dont les paramètres et la valeur de retour sont typés, et les partager dans un module.
  • Q2.1. Dans src/utils/format.ts, recopier le code fourni (deux fonctions et le type Mention), puis écrire les quatre fonctions manquantes d'après le tableau.
export type Mention = 'Très bien' | 'Bien' | 'Assez bien' | 'Passable' | 'Ajourné';

export function formaterMontant(montant: number, devise: string = 'DT'): string {
  return `${montant.toFixed(3)} ${devise}`;
}

export function calculerMoyenne(notes: readonly number[]): number {
  if (notes.length === 0) return 0;
  return notes.reduce((somme, note) => somme + note, 0) / notes.length;
}

// À vous : formaterDate, mention, formaterIdentifiant, initiales
Fonction Signature Comportement
formaterDate (date: Date): string renvoie la date au format jj/mm/aaaa (utiliser padStart)
mention (moyenne: number): Mention ≥ 16 « Très bien », ≥ 14 « Bien », ≥ 12 « Assez bien », ≥ 10 « Passable », sinon « Ajourné »
formaterIdentifiant (id: string | number): string un nombre devient STG-007 (3 chiffres) ; une chaîne est mise en majuscules
initiales (prenom: string, nom?: string): string A.B. si le nom est fourni, sinon A.
  • Q2.2. Créer src/exercices/p02-fonctions.ts avec le code de test suivant, compiler et exécuter. Le résultat doit correspondre à la capture.
// Partie 02 – Fonctions typées
import { calculerMoyenne, formaterDate, formaterIdentifiant, formaterMontant, initiales, mention } from '../utils/format.js';

const notesAmira = [14, 16, 17];
const moyenne = calculerMoyenne(notesAmira);
console.log(`Moyenne : ${moyenne.toFixed(2)} – mention : ${mention(moyenne)}`);
console.log(`Moyenne sans note : ${calculerMoyenne([])} – mention : ${mention(0)}`);
console.log(`Frais : ${formaterMontant(1200)} – ${formaterMontant(350, '€')}`);
console.log(`Date de rentrée : ${formaterDate(new Date(2026, 8, 15))}`);
console.log(`Identifiants : ${formaterIdentifiant(7)} – ${formaterIdentifiant('stg-042')}`);
console.log(`Initiales : ${initiales('Amira', 'Ben Ali')} – ${initiales('Sami')}`);

type Operation = (a: number, b: number) => number;
const operations: Record<string, Operation> = {
  addition: (a, b) => a + b,
  moyenne: (a, b) => (a + b) / 2,
};
console.log(`Opérations : ${operations['addition'](12, 15)} et ${operations['moyenne'](12, 15)}`);
  • Q2.3. (R) Dans formaterIdentifiant, pourquoi peut-on appeler id.toUpperCase() après le if (typeof id === 'number') alors que id est de type string | number? Comment s'appelle ce mécanisme ?
  • Q2.4. (R) Appeler mention('15'): quel message obtient-on ? Supprimer une branche return de mention: quel message obtient-on (option noImplicitReturns) ? Remettre le code correct.
  • Q2.5. (R) À quoi sert le mot-clé readonly dans le paramètre notes: readonly number[]? Essayer d'écrire notes.push(20) dans la fonction.
  • Résultat attendu à la fin de la partie 02
Résultat attendu à la fin de la partie 02 du TP TypeScript
Les six fonctions de utils/format.ts testées depuis un autre module.
Git – fin de la partie 02 :
git add .
git commit -m "Partie 02 : fonctions typées (utils/format.ts)"
git push
  • Partie 03 Modèles de données
  • Objectif : décrire la forme des données de l'application avec des interfaces et des alias de type, puis créer un jeu de données typé.
  • Q3.1. Créer src/models/personne.ts:
export interface Identifiable {
  readonly id: number;
}

export interface Personne extends Identifiable {
  nom: string;
  prenom: string;
  email: string;
  telephone?: string;
}

export interface Stagiaire extends Personne {
  groupe: string;
  notes: number[];
  dateInscription: Date;
}

export interface Formateur extends Personne {
  specialites: string[];
  tarifHoraire: number;
}
  • Q3.2. Écrire vous-même les deux autres modèles :
    • src/models/formation.ts: un type Niveau ('Débutant' | 'Intermédiaire' | 'Avancé') et une interface Formation qui étend Identifiable avec titre, niveau, dureeHeures, prixHT et formateurId;
    • src/models/inscription.ts: les types StatutInscription ('en attente' | 'validée' | 'annulée') et ModePaiement ('especes' | 'carte' | 'facilite'), une interface Inscription (stagiaireId, sessionId, statut, mode facultatif) et le type générique Resultat<T> ci-dessous.
export type Resultat<T> =
  | { succes: true; valeur: T }
  | { succes: false; erreur: string };
Une union discriminée : Resultat<T> vaut soit un succès avec une valeur, soit un échec avec un message d'erreur. La propriété succes permet à TypeScript de savoir lequel des deux cas on manipule : après if (r.succes), il sait que r.valeur existe. Les opérations du TP (inscrire, payer) renvoient un Resultat au lieu de lever une exception.
  • Q3.3. Créer src/data/donnees.ts avec le code fourni, puis compléter le tableau stagiaires avec les quatre stagiaires suivants (attention : new Date(2026, 8, 16) correspond au 16 septembre, car les mois commencent à 0) :
import type { Formateur, Stagiaire } from '../models/personne.js';
import type { Formation } from '../models/formation.js';

export const formateurs: Formateur[] = [
  { id: 1, nom: 'Gharbi', prenom: 'Ali', email: 'ali.gharbi@formaplus.tn', specialites: ['Angular', 'TypeScript'], tarifHoraire: 45 },
  { id: 2, nom: 'Mansour', prenom: 'Leila', email: 'leila.mansour@formaplus.tn', specialites: ['Réseaux', 'Linux'], tarifHoraire: 40 },
];

export const formations: Formation[] = [
  { id: 1, titre: 'Développement Angular', niveau: 'Intermédiaire', dureeHeures: 120, prixHT: 1200, formateurId: 1 },
  { id: 2, titre: 'TypeScript pour débutants', niveau: 'Débutant', dureeHeures: 40, prixHT: 450, formateurId: 1 },
  { id: 3, titre: 'Administration Linux', niveau: 'Avancé', dureeHeures: 80, prixHT: 900, formateurId: 2 },
];

export const stagiaires: Stagiaire[] = [
  { id: 1, nom: 'Ben Ali', prenom: 'Amira', email: 'amira@exemple.tn', groupe: 'DEV1', notes: [14, 16, 17], dateInscription: new Date(2026, 8, 15) },
  // À vous : les stagiaires 2 à 5 (tableau ci-dessous)
];
id nom prenom email telephone groupe notes dateInscription
2 Trabelsi Sami sami@exemple.tn +216 20 000 000 DEV1 8, 11, 9 16/09/2026
3 Jaziri Yasmine yasmine@exemple.tn — DEV2 12, 10, 15 18/09/2026
4 Haddad Karim karim@exemple.tn +216 50 000 000 RES1 18, 16, 19 20/09/2026
5 Bouazizi Nour nour@exemple.tn — RES1 aucune 01/10/2026
  • Q3.4. Créer src/exercices/p03-modeles.ts avec le code ci-dessous, compiler et exécuter.
// Partie 03 – Modèles de données
import { formateurs, formations, stagiaires } from '../data/donnees.js';
import type { Personne } from '../models/personne.js';
import { calculerMoyenne, formaterMontant, mention } from '../utils/format.js';

const contact = (p: Personne): string => `${p.prenom} ${p.nom.toUpperCase()} <${p.email}>${p.telephone ? ' – ' + p.telephone : ''}`;

console.log('--- Formateurs ---');
formateurs.forEach((f) => console.log(`${contact(f)} – ${f.specialites.join(', ')}`));

console.log('--- Formations ---');
for (const f of formations) {
  const formateur = formateurs.find((x) => x.id === f.formateurId);
  console.log(`${f.titre} (${f.niveau}, ${f.dureeHeures} h) – ${formaterMontant(f.prixHT)} HT – ${formateur?.prenom ?? '?'}`);
}

console.log('--- Stagiaires ---');
for (const s of stagiaires) {
  const moy = calculerMoyenne(s.notes);
  const resultat = s.notes.length ? `${moy.toFixed(2)} (${mention(moy)})` : 'aucune note';
  console.log(`${contact(s)} – ${s.groupe} – ${resultat}`);
}
  • Q3.5. (R) Pourquoi la fonction contact, qui attend une Personne, accepte-t-elle aussi bien un formateur qu'un stagiaire ? Pourquoi écrit-on import type pour les interfaces ?
  • Q3.6. (R) Dans les données, changer un niveau en 'Expert', oublier l'email d'un stagiaire, puis ajouter une propriété age: relever les trois messages (TS2322, TS2741, TS2353) et corriger.
  • Résultat attendu à la fin de la partie 03
Résultat attendu à la fin de la partie 03 du TP TypeScript
Formateurs, formations et stagiaires affichés à partir des modèles et du jeu de données typé.
Git – fin de la partie 03 :
git add .
git commit -m "Partie 03 : modèles de données et jeu de données"
git push
  • Partie 04 La classe Session
  • Objectif : écrire une classe qui protège ses données et expose des méthodes sûres.
  • Q4.1. Créer src/classes/session.ts avec le code fourni, puis ajouter:
    • l'accesseur estComplete (booléen : plus aucune place) ;
    • l'accesseur libelle: « Titre de la formation – jj/mm/aaaa » (utiliser formaterDate) ;
    • la méthode inscrire(stagiaireId: number): Resultat<number>: échec « stagiaire X déjà inscrit » ou « session complète », sinon ajout de l'identifiant et succès avec le nombre de places restantes ;
    • la méthode annuler(stagiaireId: number): boolean: retire le stagiaire et renvoie true s'il était inscrit.
import type { Formation } from '../models/formation.js';
import type { Resultat } from '../models/inscription.js';
import { formaterDate } from '../utils/format.js';

export class Session {
  private static compteur = 0;
  readonly id: number;
  private inscrits: number[] = [];

  constructor(
    public readonly formation: Formation,
    public readonly debut: Date,
    private capacite: number,
  ) {
    Session.compteur++;
    this.id = Session.compteur;
  }

  static get nombre(): number {
    return Session.compteur;
  }

  get placesRestantes(): number {
    return this.capacite - this.inscrits.length;
  }

  // À vous : estComplete, libelle, inscrire(), annuler()
}
  • Q4.2. Créer src/exercices/p04-session.ts, compiler et exécuter :
// Partie 04 – Classes
import { Session } from '../classes/session.js';
import { formations } from '../data/donnees.js';

const angular = new Session(formations[0], new Date(2026, 10, 2), 2);
const linux = new Session(formations[2], new Date(2026, 10, 9), 10);

console.log(`Session ${angular.id} : ${angular.libelle} (${angular.placesRestantes} places)`);
console.log(`Session ${linux.id} : ${linux.libelle} (${linux.placesRestantes} places)`);

for (const id of [1, 2, 1, 3]) {
  const r = angular.inscrire(id);
  if (r.succes) {
    console.log(`Stagiaire ${id} inscrit – reste ${r.valeur} place(s)`);
  } else {
    console.log(`Stagiaire ${id} refusé : ${r.erreur}`);
  }
}
console.log(`Annulation du stagiaire 2 : ${angular.annuler(2)} – places : ${angular.placesRestantes}`);
console.log(`Nombre de sessions créées : ${Session.nombre}`);
  • Q4.3. (R) Dans la boucle, pourquoi peut-on lire r.valeur dans le premier bloc et r.erreur dans le second, mais pas r.valeur avant le if? Écrire console.log(r.valeur); juste après const r = … et relever le message.
  • Q4.4. (R) Écrire à la fin du fichier angular.capacite = 50; puis angular.id = 7;: relever les deux messages. Ouvrir dist/classes/session.js: que sont devenus private et readonly? Que se passerait-il si un fichier JavaScript (non compilé par TypeScript) modifiait capacite?
  • Q4.5. (R) Pourquoi le compteur est-il static? Quelle valeur afficherait Session.nombre si le compteur n'était pas statique ?
  • Résultat attendu à la fin de la partie 04
Résultat attendu à la fin de la partie 04 du TP TypeScript
Deux sessions créées, inscriptions acceptées puis refusées (doublon, session complète) et annulation.
Git – fin de la partie 04 :
git add .
git commit -m "Partie 04 : classe Session"
git push
  • Partie 05 Les paiements : héritage et abstraction
  • Objectif : modéliser plusieurs modes de paiement qui partagent un comportement commun, avec une classe abstraite et une interface.
  • Q5.1. Créer src/classes/paiement.ts avec le code fourni : une interface Exportable, la classe abstraite Paiement et la classe PaiementEspeces.
import { formaterMontant } from '../utils/format.js';

export interface Exportable {
  exporterCsv(): string;
}

export abstract class Paiement implements Exportable {
  static readonly TVA = 0.19;

  constructor(protected readonly montantHT: number) {}

  abstract get libelle(): string;
  abstract frais(): number;

  get montantTTC(): number {
    return this.montantHT * (1 + Paiement.TVA);
  }

  total(): number {
    return this.montantTTC + this.frais();
  }

  recu(): string {
    return `${this.libelle.padEnd(22)} TTC ${formaterMontant(this.montantTTC)} + frais ${formaterMontant(this.frais())} = ${formaterMontant(this.total())}`;
  }

  exporterCsv(): string {
    return [this.libelle, this.montantHT, this.total().toFixed(3)].join(';');
  }
}

export class PaiementEspeces extends Paiement {
  get libelle(): string {
    return 'Espèces';
  }
  frais(): number {
    return 0;
  }
}

// À vous : PaiementCarte et PaiementFacilite
  • Q5.2. Écrire les deux classes manquantes :
    • PaiementCarte: libellé « Carte bancaire », frais de 1,5 % du montant TTC ;
    • PaiementFacilite: reçoit en plus le nombre de mensualites (propriété de paramètre privée, en lecture seule) et appelle super(montantHT); libellé « Facilité N fois », frais de 10 DT par mensualité, une méthode echeance() (total ÷ mensualités) et une redéfinition de recu() qui ajoute (3 × 486.000 DT) au reçu parent.
  • Q5.3. Créer src/exercices/p05-paiements.ts, compiler et exécuter :
// Partie 05 – Héritage, classes abstraites et interfaces
import { type Exportable, Paiement, PaiementCarte, PaiementEspeces, PaiementFacilite } from '../classes/paiement.js';

const paiements: Paiement[] = [new PaiementEspeces(1200), new PaiementCarte(1200), new PaiementFacilite(1200, 3)];
paiements.forEach((p) => console.log(p.recu()));

const aExporter: Exportable[] = paiements;
console.log('libelle;montantHT;total');
aExporter.forEach((e) => console.log(e.exporterCsv()));
  • Q5.4. (R) Provoquer et relever les messages suivants, puis corriger :
    • retirer override devant recu() dans PaiementFacilite (TS4114) ;
    • supprimer la méthode frais() de PaiementCarte (TS2515) ;
    • écrire new Paiement(100) dans le fichier de test (TS2511).
  • Q5.5. (R) La classe Paiement implémente Exportable: les classes enfants doivent-elles l'écrire elles aussi ? Pourquoi peut-on ranger les paiements dans un tableau Exportable[]? Quel est l'intérêt de protected pour montantHT plutôt que private?
  • Résultat attendu à la fin de la partie 05
Résultat attendu à la fin de la partie 05 du TP TypeScript
Trois reçus calculés par la même méthode recu(), puis export CSV grâce à l'interface Exportable.
Git – fin de la partie 05 :
git add .
git commit -m "Partie 05 : paiements, classe abstraite et interface Exportable"
git push
  • Partie 06 Un dépôt générique
  • Objectif : écrire une seule classe capable de stocker n'importe quel type de données identifiées (stagiaires, formations, inscriptions…).
  • Q6.1. Créer src/classes/depot.ts avec le code fourni, puis ajouter:
    • filtrer(critere: (element: T) => boolean): T[];
    • modifier(id: number, changements: Partial<Omit<T, 'id'>>): T | undefined: crée une nouvelle version de l'élément avec spread et remplace l'ancienne avec map;
    • supprimer(id: number): boolean.
import type { Identifiable } from '../models/personne.js';

export class Depot<T extends Identifiable> {
  private elements: T[] = [];

  constructor(elementsInitiaux: T[] = []) {
    this.elements = [...elementsInitiaux];
  }

  get taille(): number {
    return this.elements.length;
  }

  tous(): readonly T[] {
    return this.elements;
  }

  trouver(id: number): T | undefined {
    return this.elements.find((e) => e.id === id);
  }

  ajouter(donnees: Omit<T, 'id'>): T {
    const id = Math.max(0, ...this.elements.map((e) => e.id)) + 1;
    const element = { ...donnees, id } as T;
    this.elements.push(element);
    return element;
  }

  // À vous : filtrer(), modifier(), supprimer()
}
  • Q6.2. Créer src/exercices/p06-depot.ts, compiler et exécuter :
// Partie 06 – Génériques et types utilitaires
import { Depot } from '../classes/depot.js';
import { formations, stagiaires } from '../data/donnees.js';
import type { Formation } from '../models/formation.js';
import type { Stagiaire } from '../models/personne.js';

const depotStagiaires = new Depot<Stagiaire>(stagiaires);
const depotFormations = new Depot<Formation>(formations);

const nouveau = depotStagiaires.ajouter({
  nom: 'Khelifi', prenom: 'Ines', email: 'ines@exemple.tn', groupe: 'DEV2', notes: [], dateInscription: new Date(2026, 9, 20),
});
console.log(`Ajout : ${nouveau.prenom} avec l'id ${nouveau.id} – total : ${depotStagiaires.taille}`);

const modifie = depotStagiaires.modifier(2, { groupe: 'DEV2', telephone: '+216 21 111 111' });
console.log(`Modification : ${modifie?.prenom} est maintenant en ${modifie?.groupe}`);
console.log(`Modification d'un id inconnu : ${depotStagiaires.modifier(99, { groupe: 'X' }) ?? 'introuvable'}`);

const dev2 = depotStagiaires.filtrer((s) => s.groupe === 'DEV2').map((s) => s.prenom);
console.log(`Groupe DEV2 : ${dev2.join(', ')}`);
console.log(`Suppression de l'id 5 : ${depotStagiaires.supprimer(5)} – puis encore : ${depotStagiaires.supprimer(5)}`);

const debutant = depotFormations.filtrer((f) => f.niveau === 'Débutant')[0];
console.log(`Formation débutant : ${debutant?.titre}`);
console.log(`Stagiaires : ${depotStagiaires.tous().map((s) => s.id).join(', ')}`);
  • Q6.3. (R) Expliquer le rôle de T extends Identifiable. Écrire new Depot<string>() et relever le message (TS2344).
  • Q6.4. (R) Pourquoi la méthode ajouter reçoit-elle un Omit<T, 'id'>? Appeler depotStagiaires.ajouter({ nom: 'X' }) et relever le message (TS2739). Pourquoi modifier utilise-t-il Partial?
  • Q6.5. (R) La méthode tous() renvoie un readonly T[]: essayer depotStagiaires.tous().push(…) depuis le fichier de test. Pourquoi est-ce une bonne protection ?
  • Résultat attendu à la fin de la partie 06
Résultat attendu à la fin de la partie 06 du TP TypeScript
Ajout avec un identifiant automatique, modification partielle, filtre et suppression sur un Depot<Stagiaire>.
Git – fin de la partie 06 :
git add .
git commit -m "Partie 06 : dépôt générique et types utilitaires"
git push
  • Partie 07 Le service d'inscription
  • Objectif : assembler les classes dans un service qui applique les règles métier, en dépendant d'une interface pour les notifications.
  • Q7.1. Créer src/services/notificateur.ts:
export interface Notificateur {
  readonly canal: string;
  envoyer(destinataire: string, message: string): void;
}

export class NotificateurConsole implements Notificateur {
  readonly canal = 'console';
  envoyer(destinataire: string, message: string): void {
    console.log(`  ✉ [${this.canal}] à ${destinataire} : ${message}`);
  }
}

export class NotificateurJournal implements Notificateur {
  readonly canal = 'journal';
  private messages: string[] = [];

  envoyer(destinataire: string, message: string): void {
    this.messages.push(`${destinataire} – ${message}`);
  }

  get historique(): readonly string[] {
    return this.messages;
  }
}
  • Q7.2. Créer src/services/inscription.service.ts avec le code fourni, puis ajouter:
    • payer(inscriptionId: number, mode: ModePaiement): Resultat<Paiement>: échec si l'inscription est introuvable ou n'est plus « en attente » ; sinon création du paiement, passage du statut à « validée » avec modifier(), notification du stagiaire et succès avec le paiement ;
    • une méthode privée creerPaiement(mode: ModePaiement, montantHT: number): Paiement qui utilise un switch sur les trois modes (la facilité se fait en 3 fois) ;
    • parStatut(): Record<string, number> qui compte les inscriptions de chaque statut.
import { Depot } from '../classes/depot.js';
import { type Paiement, PaiementCarte, PaiementEspeces, PaiementFacilite } from '../classes/paiement.js';
import { Session } from '../classes/session.js';
import type { Inscription, ModePaiement, Resultat } from '../models/inscription.js';
import type { Stagiaire } from '../models/personne.js';
import { formaterMontant } from '../utils/format.js';
import type { Notificateur } from './notificateur.js';

export class InscriptionService {
  private readonly inscriptions = new Depot<Inscription>();

  constructor(
    private readonly stagiaires: Depot<Stagiaire>,
    private readonly sessions: Session[],
    private readonly notificateur: Notificateur,
  ) {}

  inscrire(stagiaireId: number, sessionId: number): Resultat<Inscription> {
    const stagiaire = this.stagiaires.trouver(stagiaireId);
    const session = this.sessions.find((s) => s.id === sessionId);
    if (!stagiaire) return { succes: false, erreur: `stagiaire ${stagiaireId} introuvable` };
    if (!session) return { succes: false, erreur: `session ${sessionId} introuvable` };

    const place = session.inscrire(stagiaireId);
    if (!place.succes) return { succes: false, erreur: place.erreur };

    const inscription = this.inscriptions.ajouter({ stagiaireId, sessionId, statut: 'en attente' });
    this.notificateur.envoyer(stagiaire.email, `inscription en attente pour « ${session.libelle} »`);
    return { succes: true, valeur: inscription };
  }

  // À vous : payer(), creerPaiement() (privée) et parStatut()
}
  • Q7.3. Remplacer le contenu de src/main.ts par le programme ci-dessous, puis lancer npm run build et npm start.
import { Depot } from './classes/depot.js';
import { Session } from './classes/session.js';
import { formateurs, formations, stagiaires } from './data/donnees.js';
import type { Stagiaire } from './models/personne.js';
import { InscriptionService } from './services/inscription.service.js';
import { NotificateurConsole } from './services/notificateur.js';
import { calculerMoyenne, formaterMontant, mention } from './utils/format.js';

console.log('=== FormaPlus – gestion des inscriptions ===');

const depotStagiaires = new Depot<Stagiaire>(stagiaires);
const sessions = [
  new Session(formations[0], new Date(2026, 10, 2), 2),
  new Session(formations[1], new Date(2026, 10, 4), 12),
  new Session(formations[2], new Date(2026, 10, 9), 10),
];
const inscriptions = new InscriptionService(depotStagiaires, sessions, new NotificateurConsole());

console.log('\n--- Inscriptions ---');
const demandes: [number, number][] = [[1, 1], [2, 1], [3, 1], [4, 3], [9, 2]];
for (const [stagiaireId, sessionId] of demandes) {
  const r = inscriptions.inscrire(stagiaireId, sessionId);
  console.log(r.succes ? `  ✔ inscription n° ${r.valeur.id} créée` : `  ✘ refus : ${r.erreur}`);
}

console.log('\n--- Paiements ---');
const reglements: [number, 'especes' | 'carte' | 'facilite'][] = [[1, 'carte'], [3, 'facilite'], [1, 'especes']];
for (const [id, mode] of reglements) {
  const r = inscriptions.payer(id, mode);
  console.log(r.succes ? `  ✔ ${r.valeur.recu()}` : `  ✘ refus : ${r.erreur}`);
}
  • Q7.4. (R) Le switch de creerPaiement n'a pas de default, et pourtant TypeScript ne signale aucun oubli de return. Pourquoi ? Supprimer le case 'facilite' et relever le message obtenu (TS2366). Ajouter 'virement' au type ModePaiement: où le compilateur vous signale-t-il qu'il faut compléter le code ?
  • Q7.5. (R) Le service ne connaît que l'interface Notificateur. Dans main.ts, remplacer new NotificateurConsole() par une instance de NotificateurJournal, puis afficher son historique à la fin du programme. Avez-vous modifié InscriptionService? Quel est l'intérêt de cette organisation ? Revenir ensuite au NotificateurConsole.
  • Résultat attendu à la fin de la partie 07
Résultat attendu à la fin de la partie 07 du TP TypeScript
Inscriptions acceptées et refusées, notifications envoyées, paiements par carte et en trois fois, double paiement refusé.
Git – fin de la partie 07 :
git add .
git commit -m "Partie 07 : service d'inscription et notificateurs"
git push
  • Partie 08 Les décorateurs
  • Objectif : comprendre ce qu'est un décorateur en écrivant les siens, avant de retrouver ceux d'Angular (@Component, @Injectable…).
  • Un décorateur est une fonction appliquée à une classe ou à une méthode avec la syntaxe @nom. Il peut ajouter un comportement (journaliser un appel, mesurer une durée…) sans modifier le code de la méthode décorée.
  • Q8.1. Créer src/decorateurs/journal.ts avec deux décorateurs : journaliser (méthode) affiche le nom et les arguments de chaque appel ; service (classe) affiche un message quand la classe est définie.
export function journaliser<This, Args extends unknown[], Retour>(
  methode: (this: This, ...args: Args) => Retour,
  contexte: ClassMethodDecoratorContext<This, (this: This, ...args: Args) => Retour>,
) {
  const nom = String(contexte.name);
  return function (this: This, ...args: Args): Retour {
    console.log(`  ▶ ${nom}(${args.map((a) => JSON.stringify(a)).join(', ')})`);
    const resultat = methode.call(this, ...args);
    return resultat;
  };
}

export function service(nom: string) {
  return function <T extends abstract new (...args: any[]) => unknown>(classe: T, contexte: ClassDecoratorContext<T>): T {
    console.log(`[décorateur] service « ${nom} » enregistré (classe ${String(contexte.name)})`);
    return classe;
  };
}

// À vous : mesurer
  • Q8.2. Sur le même modèle que journaliser, écrire le décorateur de méthode mesurer qui exécute la méthode, puis affiche sa durée d'exécution : ⏱ nom : 0.056 ms (utiliser performance.now() avant et après l'appel).
  • Q8.3. Créer src/exercices/p08-decorateurs.ts, compiler et exécuter :
// Partie 08 – Décorateurs
import { journaliser, mesurer, service } from '../decorateurs/journal.js';

@service('calculs')
class Calculatrice {
  @journaliser
  @mesurer
  moyenne(...notes: number[]): number {
    return notes.reduce((s, n) => s + n, 0) / notes.length;
  }
}

const calc = new Calculatrice();
console.log(`Résultat : ${calc.moyenne(14, 16, 17).toFixed(2)}`);
  • Q8.4. Dans inscription.service.ts, importer les décorateurs et les appliquer : @service('inscriptions') sur la classe, @journaliser sur inscrire() et payer(). Relancer npm run build et npm start.
  • Q8.5. (R) Le message « service enregistré » s'affiche avant le titre du programme : pourquoi ? Dans p08-decorateurs.ts, inverser l'ordre de @journaliser et @mesurer: l'affichage change-t-il ? Dans quel ordre les décorateurs sont-ils appliqués ?
  • Q8.6. (R) Citer trois décorateurs d'Angular et ce qu'ils indiquent au framework.
Deux systèmes de décorateurs : ce TP utilise les décorateurs standard d'ECMAScript, pris en charge par TypeScript depuis la version 5.0, sans option particulière. Angular utilise encore l'ancienne version « expérimentale » (option experimentalDecorators dans le tsconfig.json d'un projet Angular), dont la signature est différente. Dans Angular, vous utiliserez des décorateurs fournis par le framework, sans avoir à les écrire.
  • Résultat attendu à la fin de la partie 08
Résultat attendu à la fin de la partie 08 du TP TypeScript
Le décorateur de classe s'exécute à la définition de la classe ; les décorateurs de méthode journalisent et chronomètrent chaque appel.
Git – fin de la partie 08 :
git add .
git commit -m "Partie 08 : décorateurs journaliser, mesurer et service"
git push
  • Partie 09 Programme principal et vérifications
  • Objectif : terminer le programme avec un tableau de bord, et vérifier la qualité du projet.
  • Q9.1. Compléter src/main.ts: ajouter l'import de formateurs et de calculerMoyenne, formaterMontant, mention, puis, à la fin du fichier, le tableau de bord suivant :
console.log('\n--- Tableau de bord ---');
for (const s of sessions) {
  const formateur = formateurs.find((f) => f.id === s.formation.formateurId);
  console.log(`${s.libelle.padEnd(45)} ${String(s.placesRestantes).padStart(2)} place(s) – ${formateur?.prenom} ${formateur?.nom}`);
}
console.log('Inscriptions par statut :', inscriptions.parStatut());

const classement = depotStagiaires
  .filtrer((s) => s.notes.length > 0)
  .map((s) => ({ nom: `${s.prenom} ${s.nom}`, moyenne: calculerMoyenne(s.notes) }))
  .toSorted((a, b) => b.moyenne - a.moyenne);
console.log('Classement :');
classement.forEach((c, i) => console.log(`  ${i + 1}. ${c.nom.padEnd(16)} ${c.moyenne.toFixed(2)} (${mention(c.moyenne)})`));

const chiffreAffaires = formations.reduce((t, f) => t + f.prixHT, 0);
console.log(`Catalogue : ${formations.length} formations, ${formaterMontant(chiffreAffaires)} HT au total`);
  • Q9.2. Lancer npm run verifier: (R) que fait ce script, et pourquoi est-il plus rapide que npm run build? Dans quelle situation l'utiliser ?
  • Q9.3. (R) Le classement utilise toSorted(), une méthode d'ES2023. Remplacer temporairement "target": "ES2023" par "ES2022" dans tsconfig.json et compiler : quel message obtenez-vous ? Que vérifie TypeScript grâce à l'option target? Remettre ES2023.
  • Q9.4. (R) Provoquer une erreur à l'exécution : dans payer(), remplacer this.sessions.find(…)! par une recherche d'un identifiant inexistant, puis lancer node --enable-source-maps dist/main.js. La pile d'erreur indique-t-elle le fichier .ts ou le fichier .js? À quoi sert le fichier .map? Que signifie le ! après find(…), et pourquoi est-il risqué ? Revenir au code correct.
  • Résultat attendu à la fin de la partie 09
Résultat attendu à la fin de la partie 09 du TP TypeScript
Vérification des types sans erreur, puis tableau de bord : places restantes, inscriptions par statut, classement et chiffre du catalogue.
Git – fin de la partie 09 :
git add .
git commit -m "Partie 09 : programme principal et vérifications"
git push
  • Partie 10 Finalisation et version 1.0 sur GitHub
  • Q10.1. Faire les captures dans captures/: npm start complet, un exemple d'erreur de compilation relevé pendant le TP, et l'arborescence du projet dans VS Code.
  • Q10.2. Rédiger le README.md en suivant le modèle ci-dessous (à gauche).
  • Q10.3. Vérifier que reponses.md est complet, et qu'aucun fichier de node_modules/ ou de dist/ n'apparaît dans le dépôt : git ls-files | findstr "node_modules dist" sous Windows (git ls-files | grep -E "node_modules|dist" sous Linux et macOS) ne doit rien afficher.
  • Q10.4. Faire le dernier commit, puis marquer la version finale avec l'étiquette v1.0 et l'envoyer sur GitHub (à droite).
# Gestion des inscriptions FormaPlus

Programme TypeScript réalisé dans le cadre du TP TypeScript :
gestion des stagiaires, des sessions, des inscriptions et des paiements.

## Notions utilisées
- Types, unions, types littéraux, unknown
- Interfaces, extends, Resultat<T> (union discriminée)
- Classes, private, readonly, static, accesseurs
- Classe abstraite, héritage, override, implements
- Classe générique Depot<T>, Omit, Partial
- Décorateurs de classe et de méthode

## Lancer le projet
npm install
npm run build
npm start

## Captures
![Exécution](captures/10_execution.png)

## Auteur
Prénom Nom – Groupe
git add .
git commit -m "Partie 10 : README, captures et version 1.0"
git push

git tag v1.0
git push origin v1.0
  • Résultat attendu à la fin de la partie 10
Résultat attendu à la fin de la partie 10 du TP TypeScript
Historique d'un commit par partie, étiquette v1.0 envoyée sur GitHub, aucun fichier de node_modules/ ni de dist/ versionné.
  1. Exercices complémentaires (bonus)

  • Annulation : ajouter une méthode annuler(inscriptionId) au service, qui libère la place dans la session (méthode annuler de Session), passe le statut à « annulée » et prévient le stagiaire. Une inscription validée ne peut pas être annulée.
  • Nouveau mode de paiement : ajouter 'virement' à ModePaiement et une classe PaiementVirement (frais fixes de 2,500 DT). Laisser le compilateur vous guider vers tous les endroits à compléter.
  • Décorateur avec paramètre : écrire un décorateur @limiter(3) qui refuse d'exécuter une méthode plus de 3 fois et renvoie un échec Resultat.
  • Sauvegarde : écrire une classe StockageFichier qui enregistre les inscriptions dans un fichier inscriptions.json avec le module node:fs (writeFileSync, readFileSync) et les relit au démarrage.
  • Tests automatiques : installer Vitest (npm install -D vitest) et écrire des tests pour mention(), Session.inscrire() et Depot.modifier().
  1. Compte rendu

  • À rendre :
    • le lien de votre dépôt GitHub gestion-formaplus, envoyé dès la partie 00 : c'est ce dépôt qui est corrigé, dans l'état de l'étiquette v1.0;
    • dans le dépôt, le fichier reponses.md: réponses aux questions marquées (R), soit Q0.3, Q0.4, Q0.8, Q1.2, Q1.3, Q1.4, Q2.3, Q2.4, Q2.5, Q3.5, Q3.6, Q4.3, Q4.4, Q4.5, Q5.4, Q5.5, Q6.3, Q6.4, Q6.5, Q7.4, Q7.5, Q8.5, Q8.6, Q9.2, Q9.3, Q9.4 et les questions de réflexion ;
    • dans le dépôt, le code complet dans src/, le README.md et les captures du dossier captures/.
  • Barème indicatif (/20) :
Critère Points
Partie 00 – projet TypeScript, tsconfig.json, scripts npm, dépôt GitHub 1,5
Partie 01 – types de base, unions, tuples, any / unknown 1,5
Partie 02 – fonctions typées, paramètres facultatifs, rétrécissement 1,5
Partie 03 – interfaces, extends, types littéraux, données complètes 2
Partie 04 – classe Session: encapsulation, accesseurs, Resultat 2
Partie 05 – classe abstraite, héritage, override, implements 2,5
Partie 06 – dépôt générique, contrainte, Omit / Partial 2
Partie 07 – service, union discriminée, interface Notificateur 2,5
Partie 08 – décorateurs journaliser, mesurer et service 1,5
Partie 09 – programme principal complet, vérifications 1
Git / GitHub – un commit par partie avec un message clair, ni node_modules/ ni dist/ versionnés, README.md, étiquette v1.0 2
Total 20
Bonus +2 max
Question de réflexion (à rédiger en 5 lignes) :
Pendant ce TP, le compilateur TypeScript a refusé plusieurs de vos programmes avant même leur exécution. Choisissez une erreur précise rencontrée pendant la séance, expliquez ce qui se serait passé à l'exécution si le même code avait été écrit en JavaScript, et dites en quoi cela vous sera utile dans une application Angular.