Dans cette nouvelle partie, nous allons enrichir notre application Angular en ajoutant deux nouveaux composants (Selecteur et ZoneText) et en modifiant le service YAML pour mieux gérer les tests. Nous verrons également comment ces modifications impactent les composants existants exercice-sortie
et exercice-code
.
1. Ajout des nouveaux composants
1.1 Composant Selecteur
Ce composant permet à l'utilisateur de choisir une option dans une liste déroulante. Il est notamment utilisé dans le composant exercice-code
pour sélectionner le langage de programmation souhaité.
HTML - selecteur.component.html
<select [ngModel]="selectedOption" (ngModelChange)="onChange($event)">
<option [value]="option.toLowerCase()" *ngFor="let option of options">{{ option }}</option>
</select>
CSS - selecteur.component.css
select {
padding: 0.5vh 2vh;
border: none;
background: #121212;
border-radius: 5px;
color: white;
}
TypeScript - selecteur.component.ts
import { Component, EventEmitter, Input, OnInit, Output } from '@angular/core';
import { NgForOf } from '@angular/common';
import { FormsModule } from '@angular/forms';
@Component({
selector: 'app-selecteur',
imports: [
NgForOf,
FormsModule
],
templateUrl: './selecteur.component.html',
styleUrl: './selecteur.component.css'
})
export class SelecteurComponent implements OnInit {
@Input() options: string[] = [];
@Input() nom: string = "";
@Output() selecteur = new EventEmitter<string>();
selectedOption: string = "";
onChange(optionValue: string) {
this.selecteur.emit(optionValue);
}
ngOnInit(): void {
this.selectedOption = this.options[0];
}
}
Explication :
-
HTML : Utilisation du binding avec
ngModel
et d'une boucle*ngFor
pour générer les options de la liste. - CSS : J'ai realiser un style assez simple mais vous pouvez le modifier comme vous le souhaitez.
- TypeScript : Le composant initialise la sélection avec la première option et émet la valeur sélectionnée pour que le parent puisse l'utiliser.
1.2 Composant ZoneText
Le composant ZoneText
servira à afficher les sorties lorsque nous lancerons les tests. Sa fonctionnalité sera améliorée dans les prochaines versions du tutoriel.
2. Mise à jour du service YAML
Pour mieux gérer les données de nos exercices, nous avons modifié le service YAML. L'objectif est de pouvoir convertir les définitions YAML en objets exploitables par l'application, notamment pour gérer les tests.
2.1 Conversion d'un YAML en Question
Nous allons modifier la méthode convertYAMLToQuestion
.
convertYAMLToQuestion(yaml: string, url: string): Question {
const includeType = new jsyaml.Type('!include', {
kind: 'scalar',
resolve: (data) => typeof data === 'string',
construct: (data) => {
return url.replace("info.yml", "").replace("?ref_type=heads", "") + data;
}
});
const CUSTOM_SCHEMA = jsyaml.DEFAULT_SCHEMA.extend([includeType]);
const parsedData = jsyaml.load(yaml, { schema: CUSTOM_SCHEMA });
return parsedData as Question;
}
2.2 Conversion d'un YAML en Tests
La méthode convertYAMLToTest
transforme le contenu YAML en un tableau d'objets de type Tests
.
convertYAMLToTest(yaml: string): Tests[] {
let result: Tests[] = [];
try {
const parsedData: any = jsyaml.load(yaml);
const tests: Tests[] = parsedData.map((test: any) => {
const entreeStr = typeof test['entrée'] === 'string' ? test['entrée'] : String(test['entrée']);
const sortieStr = typeof test.sortie === 'string' ? test.sortie : String(test.sortie);
return {
nom: test.nom,
entree: entreeStr
.trim()
.split('\n')
.map(line => Number(line.trim()))
.filter(n => !isNaN(n)),
sortie: sortieStr
.trim()
.split('\n')
.map(line => Number(line.trim()))
.filter(n => !isNaN(n))
};
});
result = tests;
} catch (error) {
console.error("Erreur lors de la conversion du YAML :", error);
}
return result;
}
2.3 Définition de l'entité Tests
export interface Tests {
nom: string;
entree: number[];
sortie: number[];
}
Explication :
- Le service YAML est enrichi pour pouvoir traiter les tests et les intégrer dans nos exercices.
- La conversion transforme les chaînes de caractères en tableaux de nombres pour faciliter les comparaisons lors des tests.
3. Modifications des composants existants
3.1 Mise à jour du composant Exercice-Sortie
Nous avons adapté le composant exercice-sortie
pour qu'il affiche les boutons correspondant à chaque test et intègre le composant ZoneText
pour afficher du contenu supplémentaire.
HTML - exercice-sortie.component.html
<div class="d-flex w-100 flex-column h-100 mt-3 ">
<div class="d-flex flex-column align-items-center gap-2 justify-content-center text-center">
<app-bouton *ngFor="let test of tests" [title]="test.nom" class="w-100" customStyle="width: 95%;"></app-bouton>
</div>
<div>
<app-zone-text></app-zone-text>
</div>
</div>
TypeScript - exercice-sortie.component.ts
import { Component, Input, OnInit } from '@angular/core';
import { Question } from '../../entities/question';
import { ExercicesService } from '../../services/exercices.service';
import { BoutonComponent } from '../bouton/bouton.component';
import { ZoneTextComponent } from '../zone-text/zone-text.component';
import { YamlService } from '../../services/yaml.service';
import { NgForOf } from '@angular/common';
@Component({
selector: 'app-exercice-sortie',
imports: [
BoutonComponent,
ZoneTextComponent,
NgForOf
],
templateUrl: './exercice-sortie.component.html',
styleUrl: './exercice-sortie.component.css'
})
export class ExerciceSortieComponent implements OnInit {
@Input() exercice: Question = {
auteur: '',
licence: '',
niveau: '',
objectif: '',
rétroactions: { negative: '', positive: '' },
tests: '',
titre: '',
type: '',
ebauches: {},
énoncé: ''
};
tests: any;
constructor(private exerciceService: ExercicesService, private yamlService: YamlService) {}
ngOnInit() {
this.exercice = this.exerciceService.exercice;
if (this.exercice.tests) {
this.yamlService.readYAML(this.exercice.tests.replace("https://git.dti.crosemont.quebec/", "/api/"))
.subscribe((yaml) => {
this.tests = this.yamlService.convertYAMLToTest(yaml);
});
}
}
}
Explication :
- Le composant récupère l'exercice courant et, si des tests sont définis, il lit le fichier YAML correspondant pour génére les boutons de test.
3.2 Mise à jour du composant Exercice-Code
Le composant exercice-code
a été modifié pour intégrer le Selecteur. Celui-ci permet à l'utilisateur de choisir le langage de programmation dans lequel le code sera affiché grâce à l'éditeur Monaco.
HTML - exercice-code.component.html
<div style="z-index: 9999;" class="position-fixed top-0 end-0 m-2 ">
<app-selecteur (selecteur)="setLangage($event)" [options]="langages"></app-selecteur>
</div>
<ngx-monaco-editor class="code-editor" [options]="editorOptions" [(ngModel)]="code"></ngx-monaco-editor>
TypeScript - exercice-code.component.ts
import { Component, Input, OnInit } from '@angular/core';
import { Question } from '../../entities/question';
import { ExercicesService } from '../../services/exercices.service';
import { EditorComponent } from 'ngx-monaco-editor-v2';
import { FormsModule } from '@angular/forms';
import { Observable } from 'rxjs';
import { HttpClient } from '@angular/common/http';
import { SelecteurComponent } from '../selecteur/selecteur.component';
@Component({
selector: 'app-exercice-code',
imports: [
EditorComponent,
FormsModule,
SelecteurComponent
],
templateUrl: './exercice-code.component.html',
styleUrl: './exercice-code.component.css'
})
export class ExerciceCodeComponent implements OnInit {
editorOptions = { theme: 'vs-light', language: 'javascript' };
code: string = '';
langages: string[] = [];
selectedLanguage: string = 'java';
@Input() exercice: Question = {
auteur: '',
licence: '',
niveau: '',
objectif: '',
rétroactions: { negative: '', positive: '' },
tests: '',
titre: '',
type: '',
ebauches: {},
énoncé: ''
};
constructor(private exerciceService: ExercicesService, private http: HttpClient) {}
ngOnInit() {
this.exercice = this.exerciceService.exercice;
this.langages = Object.keys(this.exercice.ebauches);
this.loadExerciceCode(this.langages[0]);
}
loadExerciceCode(langage: string) {
this.readCode(this.exercice.ebauches[langage].replace("https://git.dti.crosemont.quebec/", "/api/"))
.subscribe({
next: (value) => {
this.code = value;
}
});
}
readCode(url: string): Observable<string> {
return this.http.get(url, { responseType: 'text' });
}
setLangage(value: string) {
this.selectedLanguage = value;
this.loadExerciceCode(this.selectedLanguage);
}
}
CSS - exercice-code.component.css
.code-editor {
height: 100%;
width: 100%;
}
Explication :
- Le Selecteur intégré permet de choisir dynamiquement le langage de programmation en fonction des clés présentes dans l'objet
ebauches
de l'exercice. - Une fois le langage sélectionné, le code correspondant est chargé et affiché dans l'éditeur Monaco.
Nous avons bientôt terminer cette partie, dans la prochaine veille techno nous allons nous approcher de la fin en y intégrant la compilation de code.
Références :
- GOOGLE, Angular Team, dir. Angular, 2024, https://angular.io
Commentaires