La transpilation permet de convertir du code TypeScript en JavaScript standard, compatible avec tous les navigateurs. Toutes les annotations de types sont supprimées et seule la logique JavaScript reste.
Utilité du typage fort dans TypeScript
Détecter les erreurs à la compilation plutôt qu'à l'exécution
Rendre le code plus lisible et auto-documenté
Aider à l'autocomplétion et la navigation dans l'IDE
Faciliter la maintenance et le refactoring à grande échelle
Le typage fort dans TypeScript apporte donc plus de robustesse et de sécurité au développement, contrairement à JavaScript qui est faiblement typé et dynamique.
02 Hello Astro TypeScript
Exercice : Créer une application Astro en TypeScript qui affiche dynamiquement "Bonjour, TypeScript!"
const my_hello_message: string = "Bonjour TYPESCRIPT !";
// Création d'un élément h1
const my_title: HTMLHeadingElement = document.createElement("h1");
my_title.textContent = my_hello_message;
// Création d'une div avec id="app"
const appDiv: HTMLDivElement = document.createElement("div");
appDiv.id = "app";
appDiv.appendChild(my_title);
// Insertion dans le body
document.body.appendChild(appDiv);
03 Modules
Exercice sur les modules TypeScript avec gestion de conflits de nommage entre deux fonctions identiques.
import { calculerJoursRestants as calculerVacances } from "./moduleVacances.ts";
import { calculerJoursRestants as calculerTravail } from "./moduleTravail.ts";
const vacancesRestantes = calculerVacances();
const travailRestant = calculerTravail();
const resultatVacances = document.createElement("div");
resultatVacances.textContent = \`la jours de vacances restant = \${vacancesRestantes}\`;
const resultatTravail = document.createElement("div");
resultatTravail.textContent = \`la jours de travail restant = \${travailRestant}\`;
document.body.appendChild(resultatVacances);
document.body.appendChild(resultatTravail);
// moduleTravail.ts
// Hypothèse : Un employé a droit à 30 jours de vacances par an.
export function calculerJoursRestants(): number {
const joursTravaille = 180; // par exemple, un employee a travaille 180 jours
return 250 - joursTravaille; // on suppose que un employee a 250 jours de travail par an
}
// moduleVacances.ts
// Hypothèse : Un employé a droit à 30 jours de vacances par an.
export function calculerJoursRestants(): number {
const joursPris = 10; // par exemple, un employee a pris 10 jours de vacances
return 30 - joursPris; // on suppose que un employee a 30 jours de vacances par an
}
04 Client ou Server Side
Réflexion sur l'exécution du code selon son emplacement dans le projet Astro.
Les messages console.log ajoutés dans les fonctions de
moduleWeatherClientSide.ts s'affichent dans la console du navigateur, car ces fonctions s'exécutent côté client. En revanche, ceux dans moduleWeatherServerSide.ts apparaissent dans le terminal du serveur Astro, car ils sont exécutés lors du rendu côté serveur.
1. Code côté serveur (dans le dossier src/pages)
Les fichiers dans src/pages (comme index.astro et moduleWeatherServerSide.ts) sont exécutés sur le serveur au moment du rendu.
Les console.log de ces fonctions s'affichent donc dans le terminal serveur et non dans le navigateur.
2. Code côté client (dans le dossier src/scripts)
Les fichiers comme app.ts ou moduleWeatherClientSide.ts sont exécutés dans le navigateur une fois la page chargée.
Leurs console.log s'affichent dans la console DevTools du navigateur.
En résumé, la séparation des dossiers permet de distinguer clairement le code côté serveur et côté client.
1. Visibilité des requêtes côté client
Les appels fetch() faits dans le navigateur sont visibles dans l'onglet Réseau des outils de développement, car le navigateur enregistre toutes les requêtes HTTP qu’il déclenche lui-même.
2. Requêtes côté serveur invisibles
Les requêtes faites sur le serveur (ex. dans le frontmatter d'un fichier Astro) ne passent pas par le navigateur, donc elles ne sont pas visibles dans l’onglet Réseau.
Si on voit deux requêtes fetch dans l'onglet Réseau, cela signifie que les deux ont été lancées côté client.
Pour tester les exercices du session 07, ouvrir le console pour voir les résultats apres cliquer les buttons.
Exercice 1 : Exécuter le fichier dans le navigateur
Faire le fichier app.ts soit exécuté dans le navigateur (côté client).
Ajouter une fonction fléchée, qui est utilisée comme callback dans addEventListener pour afficher les coordonnées de la souris dans la console à chaque clic.
Une interface IVehicle définit deux méthodes. Une classe abstraite Vehicle fournit
une implémentation de drive(). Les classes Car et Bicycle définissent leur propre
méthode honk(). Le programme crée plusieurs instances et les parcourt.
// Ici j'ai mis tout les messages apparaîts dans console affichés dessous le button
interface IVehicle {
drive(): void;
honk(): void;
}
abstract class Vehicle implements IVehicle {
constructor(protected speed: number) {}
abstract honk(): void;
drive(): void {
console.log(\`Driving at \${this.speed} km/h\`);
}
}
class Car extends Vehicle {
honk(): void {
console.log("Beep beep!");
}
}
class Bicycle extends Vehicle {
honk(): void {
console.log("Ring Ring");
}
}
const vehicles: IVehicle[] = [
new Car(120),
new Car(150),
new Bicycle(20),
new Bicycle(25),
new Bicycle(30)
];
for (const vehicle of vehicles) {
vehicle.drive();
vehicle.honk();
}
10 json
Exercice sur la (dé)sérialisation JSON avec classes et interfaces.
class User {
constructor(
public id: string,
public name: string,
public age: number,
public scores: number[]
) {}
getMaxScore(): number {
return Math.max(...this.scores);
}
getAverageScore(): number {
if (this.scores.length === 0) return 0;
const sum = this.scores.reduce((a, b) => a + b, 0);
return sum / this.scores.length;
}
}
const users = [
new User("1", "Alice", 25, [80, 90, 85]),
new User("2", "Bob", 30, [70, 75, 80, 65]),
new User("3", "Charlie", 22, [95, 85, 90]),
];
const serialized = JSON.stringify(users);
const plainObjects = JSON.parse(serialized);
const revivedUsers = plainObjects.map(
(data: any) => new User(data.id, data.name, data.age, data.scores)
);
const result = \`User: \${revivedUsers[1].name}, Max: \${revivedUsers[1].getMaxScore()}, Avg: \${revivedUsers[1].getAverageScore().toFixed(2)}\`;
Étapes expliquées :
Nous avons défini une interface IUser avec les propriétés id, name, age et un tableau scores. Deux méthodes getMaxScore et getAverageScore sont aussi définies.
La classe User implémente cette interface et fournit les méthodes nécessaires.
Trois instances de User ont été créées avec des données fictives.
Ces instances sont stockées dans un tableau de type IUser[].
Une fonction serializeUsers utilise JSON.stringify() pour sérialiser le tableau IUser[] en une chaîne JSON.
La fonction deserializeUsers utilise JSON.parse() pour transformer la chaîne JSON en un tableau d'objets simples.
Après la désérialisation, on ne peut plus appeler getMaxScore ou getAverageScore, car les objets ne sont plus des instances de la classe User.
Pour retrouver les méthodes, nous avons reconstruit des instances User à partir des objets désérialisés.
Nous affichons alors le nom, le score max et le score moyen du 2ᵉ utilisateur restauré : c'est bien Bob.
11 Génériques
Implémentation d'une classe générique Queue<T> avec les méthodes enqueue, dequeue et size.
class Queue<T> {
private elements: T[] = [];
enqueue(element: T): void {
this.elements.push(element);
}
dequeue(): T | undefined {
return this.elements.shift();
}
size(): number {
return this.elements.length;
}
}
// Testez votre code ici
let numberQueue = new Queue();
console.log("start numberQueue test:");
console.log("origial size:", numberQueue.size());
numberQueue.enqueue(10);
numberQueue.enqueue(20);
numberQueue.enqueue(30);
console.log("size now:", numberQueue.size());
console.log("dequeue", numberQueue.dequeue());
console.log("size now:", numberQueue.size());
console.log("dequeue:", numberQueue.dequeue());
console.log("dequeue (from new empty Queue):", new Queue().dequeue());
let stringQueue = new Queue();
console.log("start stringQueue test:");
console.log("origial size:", stringQueue.size());
stringQueue.enqueue("a");
stringQueue.enqueue('b');
stringQueue.enqueue('c');
console.log("size now:", stringQueue.size());
console.log("dequeue", stringQueue.dequeue());
console.log("size now:", stringQueue.size());
console.log("dequeue:", stringQueue.dequeue());
console.log("dequeue (from new empty Queue):", new Queue().dequeue());
12 Exceptions - Gestion d'erreurs
Une classe Rectangle vérifie les dimensions avant de calculer la surface. Les erreurs sont capturées via un bloc try-catch-finally.
class Rectangle {
constructor(private longueur: number, private largeur: number) {}
calculerSurface(): number {
if (this.longueur <= 0 || this.largeur <= 0) {
throw new Error("Dimensions invalides !");
}
return this.longueur * this.largeur;
}
essayerCalculSurface(): void {
try {
const surface = this.calculerSurface();
console.log(surface);
} catch (e) {
console.error(e.message);
} finally {
console.log("Calcul terminé");
}
}
}
// test
const rectangles = [
new Rectangle(5, 10),
new Rectangle(0, 8),
new Rectangle(4, -3),
new Rectangle(7, 12)
];
console.log("=== Test des rectangles ===");
rectangles.forEach(rect => rect.essayerCalculSurface());
13 async/await et Promises
Trois exercices sur les Promesses et la syntaxe async/await :
Q1 : somme de deux nombres avec délai.
Q2 : vérification d'un utilisateur avec then/catch.
Q3 : version orientée objet avec des classes.
// Question 1
function calculateSumAsync(a: number, b: number): Promise {
return new Promise((resolve) => {
setTimeout(() => {
resolve(a + b);
}, 3000);
});
}
async function printSum(a: number, b: number): Promise {
console.log("Calcul en cours...");
const sum = await calculateSumAsync(a, b);
console.log(`Résultat: ${sum}`);
}
console.log("=== Test Question 1 ===");
printSum(5, 7);
// Question 2
function verifyUser(username: string, password: string): Promise {
return new Promise((resolve, reject) => {
const validUsername = "admin";
const validPassword = "1234";
if (username === validUsername && password === validPassword) {
resolve();
} else {
reject("Nom d'utilisateur ou mot de passe incorrect");
}
});
}
// 2. use then/catch
console.log("=== Test Question 2 ===");
verifyUser("admin", "1234")
.then(() => console.log("Bienvenue, admin!"))
.catch(err => console.error("Erreur:", err));
verifyUser("user", "psjciwqw")
.then(() => console.log("Bienvenue!"))
.catch(err => console.error("Erreur:", err));
// Question 3 - Q1 (Version with Class)
class Calculator {
async calculateSumAsync(a: number, b: number): Promise {
return new Promise((resolve) => {
setTimeout(() => {
resolve(a + b);
}, 3000);
});
}
async printSum(a: number, b: number): Promise {
console.log("Calcul en cours...");
const sum = await this.calculateSumAsync(a, b);
console.log(`Résultat: ${sum}`);
}
}
// TEST
console.log("=== Test Question 3 - Q1 ===");
const calc = new Calculator();
calc.printSum(10, 15);
// Question 3 - Q2 (Version with Class)
class Users {
private validUsername = "admin";
private validPassword = "1234";
verifyUser(username: string, password: string): Promise {
return new Promise((resolve, reject) => {
if (username === this.validUsername && password === this.validPassword) {
resolve();
} else {
reject("Nom d'utilisateur ou mot de passe incorrect");
}
});
}
login(username: string, password: string): void {
this.verifyUser(username, password)
.then(() => console.log(`Bienvenue, ${username}!`))
.catch(err => console.error("Erreur:", err));
}
}
// TEST
console.log("=== Test Question 3 - Q2 ===");
const userSystem = new Users();
userSystem.login("admin", "1234");
userSystem.login("guest", "030dwqxsa");
14 fetch
Exercice 1
Ce script utilise l'API de l'USGS pour afficher les tremblements de terre de magnitude ≥ 4 depuis hier.
Affichage des morceaux de la playlist Top 100 France 2023 (depuis l'API de Deezer)
const container = document.createElement("ul");
fetch("https://api.deezer.com/playlist/11846226041")
.then(res => res.json())
.then(async playlist => {
// track is a url
const trackRes = await fetch(playlist.tracklist);
const trackData = await trackRes.json();
trackData.data.forEach((track: any, index: number) => {
const li = document.createElement("li");
li.innerHTML = `
...
`;
container.appendChild(li);
});
document.body.appendChild(container);
})
.catch(error => {
console.error("Erreur lors de la récupération des données :", error);
});
Afficher le playlist
1. Flowers
Miley Cyrus
Flowers
2. Bolide allemand
SDM
Liens du 100
3. Jolie (feat. Ninho)
GAULOIS
Jolie (feat. Ninho)
4. Meuda
Tiakola
Mélo
5. Another Love
Tom Odell
Long Way Down (Deluxe)
6. Secret
Louane
Secret
7. Saiyan
Heuss L'enfoiré
Saiyan
8. Nocif
Hamza
Sincèrement
9. Rush
Ayra Starr
Rush
10. Tiki Taka
Vacra
Tiki Taka
11. DIE
Gazo
KMT
12. Mr. Ocho
SDM
Liens du 100
13. Casanova
Soolking
Casanova
14. THE LONELIEST
Måneskin
THE LONELIEST
15. Decrescendo
Lomepal
Mauvais Ordre
16. Calm Down
Rema
Calm Down
17. Baby
Aya Nakamura
Baby
18. AMBER
Zola
AMBER
19. DESPECHÁ
ROSALÍA
DESPECHÁ
20. I'm Good (Blue)
David Guetta
I'm Good (Blue)
21. As It Was
Harry Styles
As It Was
22. FLEURS (feat. Tiakola)
Gazo
KMT
23. C'est carré le S
naps
En temps réel
24. Creepin'
Metro Boomin
HEROES & VILLAINS
25. Petit génie
Jungeli
Petit génie
16 - HTTP CRUD avec relations entre entités
Ce script gère la suppression, création, mise à jour de restaurants et la mise à jour des catégories et relations associées.
Afficher le code
interface AlloResto {
restaurants: Restaurant[];
categories: Category[];
restaurantCategories: RestaurantCategory[];
}
interface Category {
id?: string;
name?: string;
restaurantIds?: string[];
}
interface RestaurantCategory {
restaurantId?: string;
categoryId?: string;
}
interface Restaurant {
id?: string;
name?: string;
description?: string;
categoryIds?: string[];
}
abstract class HttpClient {
protected url: string;
protected options: RequestInit;
constructor(url: string) {
this.url = url;
this.options = {
headers: {
"Content-Type": "application/json",
},
};
}
public async execute(): Promise {
try {
const response = await fetch(this.url, this.options);
if (response.ok) {
const data: T = await response.json(); //extraction du corps de la réponse au format JSON
return data;
}
} catch (error) {
console.error("There was a problem with the fetch operation: ", error);
}
}
}
class CreateClient extends HttpClient {
constructor(url: string, data: T) {
super(url);
this.options.method = "POST";
this.options.body = JSON.stringify(data);
}
}
class ReadClient extends HttpClient {
constructor(url: string) {
super(url);
this.options.method = "GET";
}
}
class UpdateClient extends HttpClient {
constructor(url: string, data: T) {
super(url);
this.options.method = "PATCH";
this.options.body = JSON.stringify(data);
}
}
class DeleteClient extends HttpClient {
constructor(url: string) {
super(url);
this.options.method = "DELETE";
}
}
const url = "http://localhost:3000/restaurants";
const categoriesUrl = "http://localhost:3000/categories";
const restaurantCategoriesUrl = "http://localhost:3000/restaurantCategories";
async function deleteRestaurantAndUpdateRelations(restaurantId: string): Promise {
const getClient = new ReadClient(`${url}/${restaurantId}`);
const restaurant = await getClient.execute();
if (!restaurant) return;
const deleteClient = new DeleteClient(`${url}/${restaurantId}`);
const deletedRestaurant = await deleteClient.execute();
if (!deletedRestaurant) return;
console.log(`DELETE id : ${deletedRestaurant.id} name : ${deletedRestaurant.name} `);
const categoryIds = restaurant.categoryIds || [];
for (const categoryId of categoryIds) {
const getCategoryClient = new ReadClient(`${categoriesUrl}/${categoryId}`);
const category = await getCategoryClient.execute();
if (category) {
const updatedRestaurantIds = category.restaurantIds?.filter(id => id !== restaurantId) || [];
const updateCategoryClient = new UpdateClient(`${categoriesUrl}/${categoryId}`, {
restaurantIds: updatedRestaurantIds
});
await updateCategoryClient.execute();
}
}
const deleteRestaurantCategoriesClient = new DeleteClient(`${restaurantCategoriesUrl}?restaurantId=${restaurantId}`);
await deleteRestaurantCategoriesClient.execute();
}
async function createRestaurantAndRelations(data: Restaurant): Promise {
const createClient = new CreateClient(url, data);
const createdRestaurant = await createClient.execute();
if (createdRestaurant && createdRestaurant.id) {
console.log(`CREATE id : ${createdRestaurant.id} name : ${createdRestaurant.name} `);
const categoryIds = data.categoryIds || [];
for (const categoryId of categoryIds) {
const getCategoryClient = new ReadClient(`${categoriesUrl}/${categoryId}`);
const category = await getCategoryClient.execute();
if (category) {
const updatedRestaurantIds = [...(category.restaurantIds || []), createdRestaurant.id];
const updateCategoryClient = new UpdateClient(`${categoriesUrl}/${categoryId}`, {
restaurantIds: updatedRestaurantIds
});
await updateCategoryClient.execute();
const createRestaurantCategoryClient = new CreateClient(restaurantCategoriesUrl, {
restaurantId: createdRestaurant.id,
categoryId: categoryId
});
await createRestaurantCategoryClient.execute();
}
}
return createdRestaurant;
}
}
async function updateRestaurantAndRelations(restaurantId: string, updatedData: Restaurant): Promise {
const getClient = new ReadClient(`${url}/${restaurantId}`);
const originalRestaurant = await getClient.execute();
if (!originalRestaurant) return;
const updateClient = new UpdateClient(`${url}/${restaurantId}`, updatedData);
const updatedRestaurant = await updateClient.execute();
if (!updatedRestaurant) return;
console.log(`UPDATE id : ${updatedRestaurant.id} name : ${updatedRestaurant.name} `);
if (updatedData.categoryIds !== undefined) {
const originalCategoryIds = originalRestaurant.categoryIds || [];
const newCategoryIds = updatedData.categoryIds || [];
const addedIds = newCategoryIds.filter(id => !originalCategoryIds.includes(id));
const removedIds = originalCategoryIds.filter(id => !newCategoryIds.includes(id));
for (const categoryId of addedIds) {
const getCategoryClient = new ReadClient(`${categoriesUrl}/${categoryId}`);
const category = await getCategoryClient.execute();
if (category) {
const updatedRestaurantIds = [...(category.restaurantIds || []), restaurantId];
const updateCategoryClient = new UpdateClient(`${categoriesUrl}/${categoryId}`, {
restaurantIds: updatedRestaurantIds
});
await updateCategoryClient.execute();
const createRestaurantCategoryClient = new CreateClient(restaurantCategoriesUrl, {
restaurantId: restaurantId,
categoryId: categoryId
});
await createRestaurantCategoryClient.execute();
}
}
for (const categoryId of removedIds) {
const getCategoryClient = new ReadClient(`${categoriesUrl}/${categoryId}`);
const category = await getCategoryClient.execute();
if (category) {
const updatedRestaurantIds = category.restaurantIds?.filter(id => id !== restaurantId) || [];
const updateCategoryClient = new UpdateClient(`${categoriesUrl}/${categoryId}`, {
restaurantIds: updatedRestaurantIds
});
await updateCategoryClient.execute();
const deleteRestaurantCategoryClient = new DeleteClient(`${restaurantCategoriesUrl}?restaurantId=${restaurantId}&categoryId=${categoryId}`);
await deleteRestaurantCategoryClient.execute();
}
}
}
return updatedRestaurant;
}
// Exemple d'exécution : suppression, création, modification
async function main() {
// suppression
await deleteRestaurantAndUpdateRelations('3aa8');
// création
await createRestaurantAndRelations({
name: "Le Restaurant de la Joie",
description: "Un restaurant où la joie est au menu",
categoryIds: ["71b2"],
});
// mise à jour
await updateRestaurantAndRelations('12b3', {
name: "Le Grill Super Marrant",
});
}
17. Forms + API OpenAQ
Cet exercice vous montre comment interagir avec l'API OpenAQ via un formulaire.
Étant donné que l'API OpenAQ ne permet pas les requêtes directes côté navigateur (CORS), l'application utilise un serveur proxy déployé sur Vercel.
L'utilisateur entre un pays (ex: FR, US, CN…)
On récupère les locations disponibles via /api/locations
On sélectionne un site de mesure → les mesures s'affichent
Données rafraîchies toutes les 10 secondes automatiquement
Si le pays n'est pas trouvé → message d'erreur
Afficher le code
import { serve } from "bun";
const OPENAQ_API_KEY = "YOUR_API_KEY"; // 替换为你的API密钥
const PORT = 3000;
async function fetchOpenAQData(endpoint: string, params: Record) {
const url = new URL(`https://api.openaq.org/v3/${endpoint}`);
Object.entries(params).forEach(([key, value]) =>
url.searchParams.append(key, value));
const response = await fetch(url, {
headers: {
"X-API-Key": OPENAQ_API_KEY,
}
});
if (!response.ok) throw new Error(await response.text());
return response.json();
}
serve({
port: PORT,
async fetch(req) {
const url = new URL(req.url);
// 返回前端页面
if (url.pathname === "/") {
return new Response(HTML, {
headers: { "Content-Type": "text/html" }
});
}
if (url.pathname === "/locations") {
try {
const country = url.searchParams.get("country");
const data = await fetchOpenAQData("locations", {
country,
limit: "100"
});
if (data.results.length === 0) {
return new Response("Country not found", { status: 404 });
}
return Response.json(data.results.map(location => ({
id: location.id,
name: location.name
})));
} catch (error) {
return new Response(error.message, { status: 500 });
}
}
if (url.pathname === "/measurements") {
try {
const locationId = url.searchParams.get("locationId");
const data = await fetchOpenAQData("measurements", {
location_id: locationId,
limit: "10",
sort: "desc"
});
return Response.json(data.results);
} catch (error) {
return new Response(error.message, { status: 500 });
}
}
return new Response("Not found", { status: 404 });
}
});
console.log(`Server running at http://localhost:${PORT}`);
18. Web Components personnalisés
Cet exercice montre comment créer des éléments HTML personnalisés en utilisant l’API Web Components.
Deux éléments ont été définis :
<date-time> : affiche la date et l’heure actuelles, mise à jour chaque seconde
<greet-custom> : affiche une salutation contextuelle (Bonjour, Bon après-midi, Bonsoir)
selon l’heure de la journée. Il accepte un attribut name pour personnaliser le message.
Afficher le code de <greet-custom>
class HelloWorld extends HTMLElement {
// le navigateur appelle cette méthode à chaque fois que l'élément est ajouté
// au DOM (c'est à dire inséré dans la page)
// vous pouvez aussi appeler cette méthode manuellement
// pour mettre à jour l'élément
connectedCallback() {
this.textContent = "Hello World!";
}
}
// ici on enregistre notre élément dans le navigateur
// pour qu'il soit reconnu et utilisable dans le HTML
// le nom de la balise est le premier paramètre
// le deuxième paramètre est la classe qui définit le comportement de l'élément
customElements.define("hello-world", HelloWorld);
class HelloName extends HTMLElement {
name: string = "Remi";
constructor() {
super();
}
// le navigateur appelle cette méthode pour savoir
// quels attributs observer (c'est à dire surveiller les changements de valeur)
// et pour ensuite appeler attributeChangedCallback
static get observedAttributes(): string[] {
return ["name"];
}
// le navigateur appelle cette méthode à chaque fois qu'un attribut
// observé change de valeur (voir observedAttributes)
// puis il appelle connectedCallback pour mettre à jour l'élément
attributeChangedCallback(name: string, oldValue: string, newValue: string) {
if (name === "name") {
console.log(`Attribute ${name} changed from ${oldValue} to ${newValue}`);
this.name = newValue;
}
}
// cette méthode est appelée à chaque fois que l'attribut name change
// (voir ci-dessus)
connectedCallback() {
console.log(`Hello ${this.name}!`);
this.textContent = `Hello ${this.name}!`;
}
}
customElements.define("hello-name", HelloName);
//ajouter un élément programmatiquement
const helloName : HelloName = document.createElement("hello-name") as HelloName;
helloName.setAttribute("name", "Monsieur DEBUT");
document.body.appendChild(helloName);
// ==================== date and time ====================
class DateTime extends HTMLElement {
private timerId?: number;
connectedCallback() {
this.updateTime();
this.timerId = setInterval(() => this.updateTime(), 1000);
}
disconnectedCallback() {
if (this.timerId) {
clearInterval(this.timerId);
}
}
private updateTime() {
this.textContent = new Date().toLocaleString();
}
}
customElements.define('date-time', DateTime);
// ==================== Active Greeting ====================
class GreetCustom extends HTMLElement {
static get observedAttributes() {
return ['name'];
}
private name: string = '';
attributeChangedCallback(name: string, oldVal: string, newVal: string) {
if (name === 'name') {
this.name = newVal;
this.updateGreeting();
}
}
connectedCallback() {
this.updateGreeting();
}
private updateGreeting() {
const hour = new Date().getHours();
let greeting = 'Bonsoir'; // default bonsoir
if (hour >= 5 && hour < 12) {
greeting = 'Bonjour';
} else if (hour >= 12 && hour < 18) {
greeting = 'Bon après-midi';
}
this.innerHTML = `