Développement d'applications web : séance 13 - Angular Material
Exercice 1 : Forum - Installations
Pour l’instant, au mieux, vous avez utilisé bootstrap pour que votre application ait
un visual « pas trop moche ». On va maintenant s’intéresser à Angular Material qui, à
peu de frais, va lui donner un look un peu moderne. Pour cela, installer via
la commande npm install les packages suivants :
@angular/material et @angular/cdk : Angular Material contient un ensemble de
composants prêts à être utilisés, qui simplifieront votre code, cf. l’URL :
@ckeditor/ckeditor5-angular et @ckeditor/ckeditor5-build-classic, qui vous
permettront d’avoir à disposition dans Angular un éditeur agréable pour saisir
vos posts, cf. l’URL :
Dans le fichier angular.json, là où vous aviez ajouté les fichiers de style
de bootstrap, ajoutez ceux d’Angular Material. Il y a plusieurs thèmes disponibles.
Celui que j’ai utilisé dans le forum de démonstration et l’indigo-pink, qui correspond
à l’ajout suivant dans angular.json :
Lisez le premier exemple de l’onglet « OVERVIEW » de cette page. La section
« Getting Started » débute par un exemple et vous noterez qu’en cliquant en haut à
droite de celui-ci sur l’icône <>, vous pourrez visualiser le template HTML et
le code TypeScript de l’exemple. Essayez de comprendre la partie HTML et la
partie TypeScript. Quand vous pensez que c’est le cas, dévoilez l’indice 1, qui
résume ce qu’il faut retenir.
Indice 1
Dans le template HTML :
"dataSource" fait référence à l’attribut de CoursComponent contenant les
données de la base de données;
Toutes les lignes à l’intérieur de la table à l’exception des deux <tr> à la
fin décrivent les colonnes de table (leur header ainsi que la manière de remplir
les valeurs des cellules de cette colonne).
les matColumnDef correspondent aux noms des colonnes de votre base de données;
dans les balises <th>, on indique les titres des colonnes;
dans les element.champ des <td>, les champs correspondent aux noms des
colonnes de votre base de données;
les deux <tr> à la fin de l’exemple correspondent respectivement à la 1ère
ligne de la table (avec les noms des colonnes), et aux lignes contenant les cours.
Dans le TypeScript :
PeriodicElement correspond à votre interface Cours;
ELEMENT_DATA est le tableau de data retourné par votre backend;
TableBasicExample correspond à CoursComponent;
dans displayedColumns, on indique les noms des colonnes de la base de
données que l’on souhaite afficher.
Modifiez votre composant CoursComponent de manière à utiliser une table Angular
Material plutôt qu’une simple table HTML. Si tout se passe bien, vous verrez
probablement apparaître dans la console Javascript un message d’erreur « Can’t bind
to “matHeaderRowDef”….. ». En effet, Angular Material semble encore utiliser la
notion de module Angular (qui a été remplacé dans tout ce que vous avez réalisé
jusqu’à maintenant par du code « standalone »). Bref, pas de panique, dans la documentation
d’Angular Material, vous pouvez voir à côté de l’onglet « OVERVIEW » (en haut de la
page) un onglet « API ». Cliquez dessus. Il vous indique l’import que vous devez
rajouter dans votre composant CoursComponent. Attention : cela signifie qu’il
faut ajouter MatTableModule au tableau d’imports de l’annotation @Component({ et
également ajouter import {MatTableModule} from '@angular/material/table'; en
haut de votre fichier cours.component.ts.
Exercice 3 : Forum - Pagination
Dans le forum de démonstration, vous pouvez observer qu’en bas des tables, il y a un
composant permettant de découper la table en plusieurs pages (un paginateur).
Pour en rajouter un, observez l’exemple de l’URL :
Là encore, lisez bien l’exemple puis dévoilez l’indice ci-dessous pour voir si vous
avez bien compris.
Indice 1
Dans le template HTML :
Seule la balise mat-paginator a besoin d’être ajoutée après la table.
Dans le TypeScript :
il faut ajouter MatPaginatorModule dans la liste des modules à importer;
dans la classe CoursComponent, il faut redéfinir le dataSource :
dataSource=newMatTableDataSource<Cours>([]);
la documentation dit d’ajouter aussi :
@ViewChild(MatPaginator)paginator!:MatPaginator;
Cela servira dans le ngOnInit() pour affecter le paginator à la table.
@ViewChild est progressivement remplacé par l’opérateur viewChild() des
view queries (cf.
https://angular.dev/guide/components/queries).
Je vous suggère donc d’utiliser plutôt le code suivant :
paginator=viewChild(MatPaginator);
dans la méthode ngOnInit(), après avoir récupéré les données backend, on
initialise le datasource et le paginator :
où res est l’objet retourné par votre MessageService. Ici, notez que c’est
bien la propriété data du dataSource qu’il faut affecter. Ensuite, on
affecte l’attribut paginator de la classe CoursComponent, c’est-à-dire le
vrai paginateur du template HTML, à la table Angular Material. Si vous avez
utilisé la version « moderne » avec viewChild(), cela devient :
Avec viewChild(), le this.paginator() peut éventuellement être undefined.
Mais le type de this.dataSource.paginator est MatPaginator | null, pas
MatPaginator | undefined. Pour pallier cela, j’ai écrit ?? null.
L’opérateur ?? évalue this.paginator(). S’il est différent de null et de
undefined, l’expression est égale à ce qui précède le ??, donc à
this.paginator(), sinon elle est égale à ce qui suit le ??, donc à null.
Si vous avez bien réalisé ces quelques modifications, dans la console, vous devriez voir
un message d’erreur vous indiquant qu’il faudrait déclarer provideAnimationsAsync(),
provideAnimations() ou provideNoopAnimations(). Il faut le faire dans le fichier
app.config.ts. Rajoutez-y le provideAnimations(). Normalement, vous devriez
voir votre paginateur et celui-ci devrait fonctionner correctement.
Exercice 4 : Forum - Tri des colonnes (optionnel)
L’URL ci-dessous vous montre comment trier les colonnes de vos tables Angular Material.
Là encore, lisez l’exemple du site d’Angular Material puis dévoilez l’indice ci-dessous.
Indice 1
Dans le template HTML :
il faut ajouter matSort dans la balise <table>;
il faut ajouter mat-sort-header dans chaque balise <th> que l’on souhaite
pouvoir trier.
Dans le TypeScript :
il faut ajouter MatSortModule à la liste des imports;
il faut ajouter @ViewChild(MatSort) sort!: MatSort; ou
sort = viewChild(MatSort); dans la classe CoursComponent;
dans le ngOnInit(), il faut ajouter this.dataSource.sort = this.sort;
ou this.dataSource.sort = this.sort() ?? null;.
Exercice 5 : Forum - Des boutons stylés (optionnel)
En haut de la page des topics, vous avez un bouton qui permet de rajouter de nouveaux
sujets. L’URL ci-dessous vous montre comment le transformer en bouton Angular Material.
Vous pouvez redéfinir les couleurs de votre bouton via les propriétés CSS color ou
background-color ou, si vous souhaitez modifier tous les boutons de votre application,
en regardant l’onglet « STYLING » de l’URL ci-dessus.
Exercice 6 : Forum - Fenêtre modale Angular (optionnel)
Pour la boite de dialogue permettant de créer un nouveau sujet, vous avez utilisé
la balise <dialog> de HTML. Angular Material propose également une boite de
dialogue, cf. l’URL :
Là encore, lisez les exemples de cette page puis dévoilez l’indice ci-dessous.
Indice 1
Il faut créer un nouveau composant qui contiendra l’intérieur de la boite de
dialogue. Appelons-le topic-dialog.
Dans le template HTML du CreateTopicDialogComponent :
Le contenu de la balise <dialog> doit être transféré dans le template HTML
de topic-dialog. Et la balise <dialog></dialog> doit être supprimée.
le bouton qui ouvrait la fenêtre doit appeler une seule méthode openDialog().
Dans le TypeScript du CreateTopicDialogComponent :
la classe DialogOverviewExample d’Angular Material correspond à votre
CreateTopicDialogComponent.
la classe DialogOverviewExampleDialog d’Angular Material correspond à votre
TopicDialogComponent.
la propriété data passée en argument de this.dialog.open contient toutes
les données que vous souhaitez transmettre au popup (ici, seul l’ID du cours
est utile).
la méthode createTopic() devrait être déplacée dans le TypeScript de
TopicDialogComponent.
dans le Typescript, il ne devrait plus rester qu’une méthode openDialog()
similaire à celle de la page d’Angular Material.
Dans le HTML topic-dialog.component.html :
il est inutile, ici, d’utiliser les <mat-form-field>, ce que vous avez
écrit fonctionne déjà bien. Vous pourrez juste associer au bouton de
fermeture sans création de nouveau sujet une méthode onCancel() qui se
contentera d’exécuter un this.dialogRef.close();, comme dans l’exemple
d’Angular Material.
Dans le TypeScript topic-dialog.component.ts :
il faut modifier la méthode createTopic() de manière à ce qu’elle n’émette
pas le résultat de l’insertion mais plutôt qu’elle fasse un
this.dialogRef.close(résultat);, où résultat est ce que l’on souhaite
transmettre aux classes parentes (ici, le bouton CreateTopicDialogComponent).
Ce bouton pourra alors émettre le résultat à son parent TopicsComponent.