La problématique des interactions

Imaginons que l’on a créé deux composants courses et cours. Inclure le composant cours dans courses est très simple, vous l’avez déjà fait : il suffit d’inclure des balises (selector) du composant cours dans le template HTML de courses.

courses.component.html
<input [(ngModel)]="idMonComp" placeholder="saisir un id"/>
<button (click)="rediriger()">Rediriger vers</button>

<app-cours />
<app-cours />

<p>{{titre()}}</p>
<ul>
  @for (module of UE; track module.nom) {
    <li>élément {{$index}} : {{module.nom}} : {{module.nb_etuds}} étuds</li>
  }
</ul>

Cela produira l’affichage suivant, dans lequel les composants cours sont dans le rectangle rouge :

Inclusion de 2 instances de cours dans courses

Toutefois, dans le TypeScript ci-dessus, on voit que la boucle @for parcourt tous les modules (des cours, donc) et on aimerait bien qu’au lieu de les afficher dans une liste, on les affiche dans des instances du composant cours. Pour cela, on peut envisager de boucler sur les <app-cours /> :

courses.component.html
<input [(ngModel)]="idMonComp" placeholder="saisir un id"/>
<button (click)="rediriger()">Rediriger vers</button>

@for (module of UE; track module.nom) {
  <app-cours />
}

Le souci, c’est qu’il faudrait informer chaque instance de cours de son nom et de son nombre d’étudiants, bref transmettre des informations du parent (courses) vers son enfant (cours). Pour cela, la solution la plus simple consiste à utiliser des inputs.

Les inputs : interactions parent -> enfant

Pour exploiter les inputs, on va commencer par dire que le CoursComponent, l’enfant, possède un ou plusieurs attributs, qui sont des inputs. Ceux-ci se comportent comme des signaux classiques à deux exceptions près :

  1. les attributs créés via des input sont en lecture seule, on ne peut pas les modifier a posteriori ;

  2. leur valeur a vocation à être affectée par le parent (ici le composant Courses).

cours.component.ts
import {Component, input} from '@angular/core';
import {Course} from '../services/mon-service.service';

@Component({
  selector: 'app-cours',
  imports: [],
  templateUrl: './cours.component.html',
  styleUrl: './cours.component.scss'
})
export class CoursComponent {
  // on crée ici un attribut dont on indique que la valeur sera un nombre et
  // sera optionnellement transmise par le parent. Ici, 0 = valeur par défaut
  index = input(0); // équivalent à input<number>(0);

  // si on veut obliger le parent à transmettre une valeur, on utilise
  // input.required à la place de input
  module = input.required<Course>();
}

Comme, dans le TypeScript du composant CoursComponent, on a un signal (input) pour l’index et un pour le module, on peut les utiliser pour afficher leur valeur dans le template HTML du CoursComponent :

cours.component.html
<ul>
  <li>élément {{index()}} : {{module().nom}} : {{module().nb_etuds}} étuds</li>
</ul>

La dernière étape du processus consiste à ce que le parent (ici Courses) transmette une valeur pour l’index et une pour le module. Cela se fait par property binding :

courses.component.html
<input [(ngModel)]="idMonComp" placeholder="saisir un id"/>
<button (click)="rediriger()">Rediriger vers</button>

@for (mymodule of UE; track mymodule.nom) {
  <app-cours [index]="$index" [module]="mymodule" />
}

Ci-dessus, [index] et [module] sont le moyen d’affecter des valeurs aux attributs index et module de CoursComponent. L’affichage obtenu avec ces codes devient celui ci-dessous, dans lequel chaque rectangle rouge correspond à une instance du composant cours :

Transmission des informations du parent vers l'enfant

Exercice 1 : Forum - les topics   

Créez un composant topics qui aura pour vocation de contenir la liste des topics d'un cours donné. Rajoutez sa route dans le fichier app.routes.ts de telle sorte que, lorsque l’on cliquera sur un cours donné dans la page des cours, on arrivera sur sa page de topics (ses topics à lui, pas ceux d’un autre cours). Pour l’instant, on ne récupère pas du backend les topics en question, on veut juste que les informations contenues dans la route permettent de le faire.

Indice 1 

Peut-être qu’ici une route paramétrée ferait l’affaire.

Solution :

Créez une route paramétrée par l’id du cours. Vous pourrez alors récupérer cet id via l'ActivatedRoute vu dans la séance précédente et c’est précisément cette information dont aura besoin votre backend pour vous fournir la liste des topics de ce cours et pas d’un autre.

Exercice 2 : Forum - des cours vers les topics   

Dans la page des cours, rajoutez à chaque cours une ancre permettant d’accéder à sa page de topics (pour l’instant, on ne récupère toujours pas leur liste, on se contente juste d’accéder à la page qui affichera tous ces topics).

Exercice 3 : Forum - le breadcrumb - partie 1 : mise en place des données   

Le haut de la page des topics doit contenir ce que l’on appelle un breadcrumb, c’est-à-dire une aide à la navigation :

Tous les cours / nom_du_cours_sélectionné

Créez un nouveau composant breadcrumb. Ce composant sera utilisé à la fois pour les topics et les posts, vous allez donc le rendre assez générique. Pour cela, dans le fichier breadcrumb.component.ts, rajoutez la déclaration de l’interface suivante :

export interface BreadcrumbData {
  nom:   string;
  route: string;
}

qui représente les données d’un item du breadcrumb (par exemple « Tous les cours » ou « nom_du_cours_sélectionné »). Le champ nom représente ce qui s’affichera sur votre navigateur et le champ route indique la route / le lien à suivre pour atteindre l’item correspondant. Par exemple la route correspondant au nom « Tous les cours » devrait être '/cours'. Les données nécessaires pour afficher tout un breadcrumb peuvent donc être stockées sous la forme d’un tableau de BreadcrumbData. Par exemple, le breadcrumb de la liste des topics du cours « Prog web et mobile » correspond au tableau :

[
  { nom: 'Tous les cours',     route: '/cours' },
  { nom: 'Prog web et mobile', route: '' }
]

Ici, on supposera que, si une route est une chaîne vide, c’est que l’item correspondant doit être affiché sans lien HTML. Dans cet exercice, contentez-vous d’ajouter l’interface BreadcrumbData.

Exercice 4 : Forum - le breadcrumb - partie 2 : transmission des données   

Faites en sorte que le BreadcrumbComponent puisse récupérer un tableau de BreadcrumbData transmis par son parent. Appelons paths l’attribut (le champ) de BreadcrumbComponent qui reçoit ce tableau. Dans la métode ngOnInit() de BreadcrumbComponent, affichez dans la console la valeur de l’attribut paths.

Pour cette séance, le parent du breadcrumb est la classe TopicsComponent. Rajoutez-lui un attribut breadcrumb de type BreadcrumbData[] et remplissez le dans sa méthode ngOnInit. Pour l’instant, on va simplifier et considérer que le nom du cours correspond à son id.

Enfin, dans le fichier topics-component.html, rajoutez une balise pour visualiser votre breadcrumb. Vérifiez que, dans la console, vous voyez bien les informations transmises au breadcrumb.

Lorsque cela fonctionne, modifiez votre attribut breadcrumb de manière à ce qu’il ne soit pas de type BreadcrumbData[] mais que ce soit plutôt un signal sur ce type.

Exercice 5 : Forum - le breadcrumb - partie 3 : l'affichage des données   

Dans le fichier breadcrumb.component.html, écrivez le code permettant l’affichage du breadcrumb. Ici, le plus simple est d’utiliser les breadcrumbs de bootstrap, cf. l’url :

https://getbootstrap.com/docs/5.3/components/breadcrumb

Indice 1 

Pour afficher tous les éléments du tableau de BreadcrumbData, pensez à exploiter le @for. Pour afficher ou non un lien HTML, le @if pourrait s’avérer utile.

Exercice 6 : Forum - la fin du breadcrumb   

On souhaite maintenant remplacer l’affichage de l’id du cours dans le breadcrumb des topics par le nom de ce cours (comme dans le forum de démonstration). Trouver une solution pour cela (réfléchissez avant de regarder la solution).

Solution :

J’insiste : réfléchissez avant de regarder la solution.

Solution :

Ici, vous en connaissez au moins 2 possibilités pour vous en sortir :

  1. La première solution consiste à utiliser le MessageService afin de requêter le backend pour obtenir à partir de son id le nom du cours. Rien n’interdit, dans la méthode ngOnInit d’appeler plusieurs fois la méthode sendMessage().

  2. La deuxième solution consiste à créer un nouveau service. Appelons-le BreadcrumbService, qui contiendra le tableau de BreadcrumbData dont on a besoin. Du coup, on doit déplacer la définition du BreadcrumbData du composant TopicsComponent vers le service BreadcrumbService. C’est plus propre, d’autant que cela servira également pour les posts. Le service nécessite la définition d’une énumération indiquant à quel niveau on est :

    export enum BreadcrumbLevel {
      Topics = 1,
      Posts = 2
    }
    

    ainsi que de 3 méthodes (au moins dans sa version basique) :

    1. setCours(idCours:number, nomCours: string) : void qui permet de mettre à jour le tableau de BreadcrumbData contenu dans le service. Cette méthode doit être appelée quand l’utilisateur sélectionne un cours sur le forum.

    2. setTopic(idTopic:number, nomTopic: string) : void qui permet de mettre à jour le tableau de BreadcrumbData contenu dans le service lorsque l’utilisateur sélectionne un topic.

    3. getPath(level: BreadcrumbLevel): BreadcrumbData[] qui retourne le tableau de BreadcrumbData dont on a besoin (qui va jusqu’au niveau indiqué).

    Il faut alors modifier CoursComponent de manière à ce que, lorsque l’on clique sur un des cours, on n’utilise plus une ancre avec un [routerLink] pour accéder aux topics correspondants, mais plutôt un event binding (click) qui exécute la méthode setCours(idCours, nomCours) du BreadcrumbService puis qui réalise un this.router.navigateByUrl().

    Enfin, dans le TopicsComponent, il ne reste plus qu’à appeler la méthode getPath(BreadcrumbLevel.Topics) du BreadcrumbService pour obtenir le breadcrumb que l’on doit passer à l’enfant BreadcrumbComponent.

Je vous suggère vivement d’essayer la 2ème méthode. Non seulement, elle est meilleure en ce sens qu’elle n’induit pas de trafic réseau ni de surcharge pour le backend, mais elle vous permet également de vous entraîner à l’utilisation des services.

Exercice 7 : Forum - les topics   

Dans le composant TopicsComponent, faites maintenant en sorte de récupérer dans son ngOnInit() la liste des topics du cours correspondant à sa route et affichez-les dans une <table> dans le template HTML (on ne s’intéresse pas à la beauté de l’application).

 
© C.G. 2007 - 2025