Interfaces

Comme dans les langages objet, il est possible de déclarer des interfaces en Angular. Cela sera particulièrement utile pour échanger des données avec le backend car on pourra annoncer le type des données transmises. Voici un exemple d’interface :

interface.ts
export interface Course {
  nom: string;
  nb_etuds : number;
}

Ici, comme on peut le voir, on définit des propriétés et leur type. Utilisons cela dans un composant :

courses.component.ts
 1import {Component, signal} from '@angular/core';
 2import {FormsModule} from '@angular/forms';
 3
 4export interface Course {
 5  nom: string;
 6  nb_etuds : number;
 7}
 8
 9@Component({
10  selector: 'app-courses',
11  imports: [
12    FormsModule
13  ],
14  templateUrl: './courses.component.html',
15  styleUrl: './courses.component.scss'
16})
17export class CoursesComponent {
18  titre = signal('composant courses');
19
20   UE : Course[] = [
21    {nom: 'c1', nb_etuds: 3},
22    {nom: 'c2', nb_etuds: 5}
23  ];
24}

Les lignes 21 et 22 créent des objets JavaScript qui implémentent l’interface. Ensuite, ces objets peuvent être utilisés comme n’importe quel objet JavaScript.

Un template HTML possible pour cette classe CoursesComponent :

courses.component.html
<p>{{titre()}}</p>
<ul>
  <li>{{UE[0].nom}} : {{UE[0].nb_etuds}} étuds</li>
  <li>{{UE[1].nom}} : {{UE[1].nb_etuds}} étuds</li>
</ul>

Ce qui donne l’affichage suivant :

Affichage du composant Courses

Les blocs de contrôle du HTML

La boucle @for  

L’exemple précédent est « bancal » en ce sens que les affichages des items du tableau UE sont encodés en dur dans le template HTML. Si on rajoute un élément dans UE, il faut mettre à jour manuellement le fichier courses.component.html. Pour pallier cela, Angular propose une boucle @for que l’on utilise dans le fichier HTML (anciennement, c’était la directive *ngFor). L’idée c’est que cette boucle s’utilise comme un for(let elt of collection) de JavaScript. Il existe toutefois une subtile différence : le @for prend un deuxième argument track qui indique à Angular où se trouvent les données du TypeScript dans le DOM. Cela lui permet d’optimiser la mise à jour du DOM. Ci-dessous, j’ai indiqué que les données sont repérées via leur index ($index) dans le tableau UE.

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

Si les données possèdent un identifiant, on peut également l’utiliser pour repérer les données :

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

Pour plus d’informations sur cette boucle, vous pouvez vous reporter à la page :

https://angular.dev/guide/templates/control-flow#repeat-content-with-the-for-block

Évidemment, le tableau UE pourrait être vide et, dans ce cas, on pourrait avoir envie de le signaler. On a deux options pour cela : utiliser le @if que l’on verra plus bas ou bien utiliser le mot-clef @empty associé au @for :

courses.component.html
<p>{{titre()}}</p>
<ul>
  @for (module of UE; track module.nom) {
    <li>élément {{$index}} : {{module.nom}} : {{module.nb_etuds}} étuds</li>
  } @empty {
    <li>Pas d'élément dans UE</li>
  }
</ul>

Affichages conditionnels avec @if et @switch  

Le mot-clef @empty est une alternative (elle n’affiche du code que lorsque la boucle @for est vide) mais elle reste limitée aux boucles @for. Angular propose des alternatives plus générales avec les mots-clefs @if, @else et @else if :

courses.component.html
<p>{{titre()}}</p>

@if (UE.length == 0) {
  <li>Pas d'élément dans UE</li>
} @else if (UE.length == 1) {
  <li>un seul élément dans UE</li>
} @else {
  <li>plusieurs éléments dans UE</li>
}

Angular propose également des switch, similaires à ceux des autres langages que vous connaissez :

courses.component.html
<p>{{titre()}}</p>

@switch (UE.length) {
  @case(0) {
    <li>Pas d'élément dans UE</li>
  }
  @case(1) {
    <li>un seul élément dans UE</li>
  }
  @default {
    <li>plusieurs éléments dans UE</li>
  }
}

Les services : providers de données

Dans le code vu plus haut, les données sont stockées en « dur » dans le TypeScript du composant courses. Ce n’est pas réaliste. Dans une vraie application, elles seraient récupérées d’un serveur (backend). C’est précisément le rôle des services de réaliser cette opération. Découpler le transfert des données de leur utilisation par les composants permet d’obtenir un code propre et maintenable. Pour générer un service en Angular, il faut utiliser la commande :

ng generate service nom_du_service

Si le nom du service contient des /, Angular créera des répertoires puis placera les fichiers du service dedans. Personnellement, je trouve pratique de placer tous les services dans un même répertoire. Par exemple :

ng generate service services/mon-service

créera un répertoire services et placera dedans deux fichiers : mon-service.service.ts et mon-service.service.spec.ts. Le deuxième sert à réaliser des tests automatiques, le premier contient le code TypeScript de votre service. Il s’agit d’une classe comme une autre dont les méthodes nous serviront à récupérer les données qui nous intéressent. Voici comment on peut modifier le code de la classe Courses afin qu’elle utilise le service pour récupérer ses données :

Version initiale du service et de son utilisation  

La première chose à faire est de créer la classe du service :

mon-service.service.ts
import { Injectable } from '@angular/core';

// placer ici l'interface indiquant le type des données que le
// composant Courses va récupérer
export interface Course {
  nom: string;
  nb_etuds : number;
}

@Injectable({
  providedIn: 'root'
})
export class MonServiceService {

  constructor() { }

  // la méthode pour récupérer les données
  getCourses() : Course[] {
    return [
      {nom: 'c1', nb_etuds: 3},
      {nom: 'c2', nb_etuds: 5}
    ];
  }
}

Ici, il y a plusieurs choses à noter :

  1. J’ai déplacé l’interface Course du composant Courses vers le service. C’est sa place logique. En effet, chaque fois que l’on échangera des données avec le serveur, c’est le service qui réalisera cette opération et il faudra indiquer le type des données que l’on recevra, donc l’interface Course.

  2. Le service est une classe dont le nom est MonServiceService. Le composant Courses devra donc utiliser une instance de cette classe pour récupérer ses données. Juste au dessus du mot-clef class, vous voyez une annotation @Injectable. Elle signifie que le composant Courses pourra (et devra) récupérer l’instance du service par dependency injection (que l’on verra plus bas).

  3. La classe MonServiceService est une classe comme les autres, on peut donc lui ajouter des méthodes pour interagir avec les composants. C’est ce que fait la méthode getCourses().

Le template HTML du composant Courses n’a pas besoin d’être modifié. En revanche, le TypeScript doit l’être afin d’utiliser le service :

courses.component.ts
import {Component, signal} from '@angular/core';
import {FormsModule} from '@angular/forms';
import {Course, MonServiceService} from '../services/mon-service.service';

@Component({
  selector: 'app-courses',
  imports: [
    FormsModule
  ],
  templateUrl: './courses.component.html',
  styleUrl: './courses.component.scss'
})
export class CoursesComponent {
  titre = signal('composant courses');

   // au départ, UE est vide. On récupérera plus tard ses données. Du coup,
   // il est bienvenu d'indiquer le type d'objets contenus dans le tableau.
   UE : Course[] = [];

   constructor() {
     // on crée le service et on l'utilise pour récupérer les données
     const service = new MonServiceService();
     this.UE = service.getCourses();
   }
}

Ici, on voit que l’attribut UE est initialisé à un tableau vide. C’est seulement dans le constructeur qu’on y place les vraies données. Pour cela, le constructeur peut créer une instance du service et on peut alors exécuter la méthode getCourses() pour placer toutes les données dans l’attribut UE.

Une version avec dependency injection  

Le code ci-dessus fonctionne mais il a toutefois deux gros problèmes :

  1. il crée dans le constructeur l’instance du service. Par conséquent, si la page que l’on affiche contient 20 instances du composant Courses, on va également créer 20 instances du service, ce qui n’est pas efficace.

  2. Le constructeur exécute la méthode getCourses(). Or, si celle-ci récupère ses données d’un serveur, ce ne sera pas forcément une opération très rapide. Malheureusement, le constructeur est bloquant pour les affichages (ceux-ci ne peuvent être réalisés tant que le constructeur n’a pas terminé son exécution). Donc le code ci-dessus peut amener à une expérience utilisateur de médiocre qualité.

Pour pallier le premier problème, on va utiliser la technique du dependency injection. L’idée est d’utiliser le design pattern Singleton, comme on l’a vu au premier semestre. Pour cela, il suffit de passer en paramètre du constructeur l’instance du service dont on a besoin. Angular générera alors cette instance une seule fois dans toute l’application et c’est cette instance qui sera utilisée chaque fois qu’on demandera une instance par dependency injection.

Pour pallier le deuxième problème, on va placer l’appel à getCourses() non pas dans le constructeur mais dans une méthode appelée ngOnInit() qui est appelée après le constructeur lorsque l’on crée les instances de Courses et qui n’est pas bloquante. Dans le code ci-dessous, pour être plus propre, j’ai indiqué que la classe Courses implémente l’interface OnInit, qui précise que Courses possède une méthode ngOnInit().

courses.component.ts
import {Component, OnInit, signal} from '@angular/core';
import {FormsModule} from '@angular/forms';
import {Course, MonServiceService} from '../services/mon-service.service';

@Component({
  selector: 'app-courses',
  imports: [
    FormsModule
  ],
  templateUrl: './courses.component.html',
  styleUrl: './courses.component.scss'
})
export class CoursesComponent implements OnInit {
  titre = signal('composant courses');

   UE : Course[] = [];

   constructor(private service : MonServiceService) {} // dependency injection

   ngOnInit() { // placer ici les appels au service
     this.UE = this.service.getCourses();
   }
}

Règle

Créez systématiquement les instances de vos services par Dependency Injection.

Les services, l'asynchronie et les observables

Évidemment, l’objectif d’un service n’est pas de stocker en « dur » les données mais bien de les demander à des backends distants. Or, communiquer sur internet prend du temps. C’est pourquoi le fonctionnement usuel de tels services est asynchrone. Vous verrez plus bas que c’est le fonctionnement des services HTTP. L’utilisation d’un service asynchrone se fait en 5 étapes :

  1. Lorsque ngOnInit exécute le service, celui-ci retourne tout de suite un Observable, avant même de récupérer les données du backend. On va voir ci-dessous ce qu’est un observable mais considérez que, pour l’instant, c’est une sorte de coquille vide.

  2. ngOnInit récupère l’observable.

  3. ngOnInit souscrit à l’observable en lui passant une callback. Celle-ci est une fonction qui sera exécutée quand l’observable contiendra les données que l’on souhaitait récupérer. Pour l’instant, elle n’est pas exécutée.

  4. ngOnInit continue son exécution sans attendre les données.

  5. Les données arrivent. Alors, seulement maintenant, l’observable émet une valeur (les données en question) et la callback est appelée avec cette valeur.

Pour illustrer ce fonctionnement, on va modifier légèrement notre service afin qu’il devienne asynchrone : on commence par indiquer que la fonction ne retourne plus un Course[] mais un Observable<Course[]>, autrement dit un Observable qui, quand les données auront été reçues, contiendra un tableau de Course. Ensuite, au lieu de retourner directement ce tableau, on retourne l’observable. Pour cela, ici, j’ai utilisé of, qui est une fonction de rxjs produisant un observable.

mon-service.service.ts
import {Observable, of} from 'rxjs';

export interface Course {
  nom: string;
  nb_etuds : number;
}

@Injectable({
  providedIn: 'root'
})
export class MonServiceService {

  constructor() { }

  getCourses() : Observable<Course[]> {
    return of([
      {nom: 'c1', nb_etuds: 3},
      {nom: 'c2', nb_etuds: 5}
    ]);
  }
}

Le code TypeScript du composant Courses doit alors être mis à jour de manière à exploiter cet observable. La partie soulignée en jaune montre comment appeler le service. On ne fait plus directement d’affectation this.UE = this.service.getCourses() mais plutôt on appelle le service et on y souscrit. C’est uniquement dans la fonction passée en paramètre du subscribe que l’on peut mettre à jour this.UE.

courses.component.ts
import {Component, OnInit, signal} from '@angular/core';
import {FormsModule} from '@angular/forms';
import {Course, MonServiceService} from '../services/mon-service.service';

@Component({
  selector: 'app-courses',
  imports: [
    FormsModule
  ],
  templateUrl: './courses.component.html',
  styleUrl: './courses.component.scss'
})
export class CoursesComponent implements OnInit {
  titre = signal('composant courses');

   UE : Course[] = [];

   constructor(private service : MonServiceService) {}

   ngOnInit() {
     this.service.getCourses().subscribe(result => {
       // ici, on met toutes les instructions qui vont utiliser les
       // données récupérées.
       console.log("ici, on a récupéré les données");
       this.UE = result;
     });

     // ici, les instructions ne doivent pas dépendre des données récupérées
     console.log("les instructions qui sont après le subscribe");
   }
}

Attention

Si vous exécutez le code ci-dessus, vous verrez sans doute le message « ici, on a récupéré les données » apparaître avant « les instructions qui sont après le subscribe » mais quand vous requêterez des données d’un backend, ce ne sera plus le cas. Il faut donc impérativement que toutes les instructions en lien avec les données récupérées du backend se trouvent dans la callback du subscribe().

Le service HTTP

Pour terminer avec les services, nous allons faire en sorte que le nôtre récupère réellement ses données sur un serveur en allant les chercher via une requête http. Pour cela, nous allons sous-traiter la partie communication frontend-backend au service HttpClient déjà existant dans Angular. À cet effet, il faut commencer par importer ce service. Cela se fait, pour toute l’application, dans le fichier app.config.ts :

app.config.ts
import { ApplicationConfig, provideZoneChangeDetection } from '@angular/core';
import { provideRouter } from '@angular/router';

import { routes } from './app.routes';
import {provideHttpClient} from '@angular/common/http';

export const appConfig: ApplicationConfig = {
  providers: [
    provideZoneChangeDetection({ eventCoalescing: true }),
    provideRouter(routes),
    // ici, il faut déclarer que notre application va utiliser
    // la librairie contenant la classe HttpClient, qui réalisera
    // effectivement les échanges entre frontend et backend
    provideHttpClient()
  ]
};

Les requêtes (GET)  

La deuxième étape va simplement consister à ce que notre service utilise le service HttpClient. Pour cela, il va en récupérer une instance par dependency injection et utiliser les méthodes get ou post de cette instance.

mon-service.service.ts
import { Injectable } from '@angular/core';
import {Observable, of} from 'rxjs';
import {HttpClient} from '@angular/common/http';

export interface Course {
  nom: string;
  nb_etuds : number;
}

@Injectable({
  providedIn: 'root'
})
export class MonServiceService {

  constructor(private http: HttpClient) { }

  getCourses() : Observable<Course[]> {
    // get retourne un observable
    return this.http.get<Course[]>('http://127.0.0.1/getCourses.php');
  }
}
getCourses.php
<?php

// on indique ici que le script va transmettre des données JSON
header('Content-type:application/json;charset=utf8');

// Ici, on ajoute un header pour contourner le problème de CORS.
// Notez que l'on doit le faire, ici, parce que le frontend Angular est servi sur
// le port 4200 alors que le backend PHP est servi par Apache sur le port 80.
header("Access-Control-Allow-Origin: " . $_SERVER['HTTP_ORIGIN']);
header("Access-Control-Allow-Credentials: true");

echo json_encode ([
  [ 'nom' => 'c1', 'nb_etuds' => 2 ],
  [ 'nom' => 'c2', 'nb_etuds' => 5 ],
  [ 'nom' => 'c3', 'nb_etuds' => 6 ]
]);

?>

Si vous exécutez ces codes, vous verrez dans la console que le message « les instructions qui sont après le subscribe » s’affichera avant « ici, on a récupéré les données », ce qui prouve bien que tout ce qui concerne les données doit être inclus dans la callback du subscribe(). Cela signifie également que le code suivant (cf. le code souligné en jaune) est incorrect car, même si UE contient 3 éléments, la page affichera via le @switch que UE est vide. En effet, l’affectation de this.nb_UE est réalisée avant que la callback ne soit appelée.

courses.component.ts
import {Component, OnInit, signal} from '@angular/core';
import {FormsModule} from '@angular/forms';
import {Course, MonServiceService} from '../services/mon-service.service';

@Component({
  selector: 'app-courses',
  imports: [
    FormsModule
  ],
  templateUrl: './courses.component.html',
  styleUrl: './courses.component.scss'
})
export class CoursesComponent implements OnInit {
  titre = signal('composant courses');

   UE : Course[] = [];
   nb_UE !: number; // le ! indique à TypeScript que c'est normal de ne pas
                    // avoir initialisé nb_UE (normalement, c'est une erreur)

   constructor(private service : MonServiceService) {}

   ngOnInit() {
     this.service.getCourses().subscribe(result => {
       // ici, on met toutes les instructions qui vont utiliser les
       // données récupérées.
       console.log("ici, on a récupéré les données");
       this.UE = result;
     });

     // ici, les instructions ne doivent pas dépendre des données récupérées
     console.log("les instructions qui sont après le subscribe");
     this.nb_UE = this.UE.length;
   }
}
courses.component.html
<p>{{titre()}}</p>
<ul>
  @for (module of UE; track module.nom) {
    <li>élément {{$index}} : {{module.nom}} : {{module.nb_etuds}} étuds</li>
  }
</ul>

@switch (nb_UE) {
  @case(0) {
    <li>Pas d'élément dans UE</li>
  }
  @case(1) {
    <li>un seul élément dans UE</li>
  }
  @default {
    <li>plusieurs éléments dans UE</li>
  }
}

Les requêtes POST et les cookies de session  

Les requêtes POST se font de la même manière que les GET, excepté que l’on doit transmettre les données qui seront stockées dans le $_POST des scripts PHP du backend. Ici, je ne transmets aucune donnée, donc je passe un null, sinon, comme dans les exercices, il faudra transmettre un objet de type form-data, cf. l’url :

https://developer.mozilla.org/fr/docs/Web/API/FormData/FormData

mon-service.service.ts
import { Injectable } from '@angular/core';
import {Observable, of} from 'rxjs';
import {HttpClient} from '@angular/common/http';

export interface Course {
  nom: string;
  nb_etuds : number;
}

@Injectable({
  providedIn: 'root'
})
export class MonServiceService {

  constructor(private http: HttpClient) { }

  getCourses() : Observable<Course[]> {
    return this.http.post<Course[]>(
      'http://127.0.0.1/xmobile_cours/getCourses.php', // l'url du backend
      null                                             // les data du POST
    );
  }
}

On a déjà vu le principe des cookies de session utilisés par PHP via sa fonction session_start(). Le code ci-dessus ne les gère pas, ce qui veut dire que si, sur la page de login de votre forum, vous saisissez un login/password correct, le script checkLogin.php vous indiquera que tout est ok, votre frontend Angular passera alors sur la page d’affichage des cours, qui requêtera le backend pour obtenir la liste des cours. Mais celui-ci vous retournera que vous n’êtes pas connecté(e) car le frontend ne renverra pas le cookie de session. Bref, il faut un moyen de le retransmettre et cela passe par un 3ème argument du post() qui contient des options. Celle qui nous intéresse s’appelle withCredentials. Si vous lui affectez la valeur true, le cookie sera retransmis et votre forum fonctionnera correctement.

mon-service.service.ts
import { Injectable } from '@angular/core';
import {Observable, of} from 'rxjs';
import {HttpClient} from '@angular/common/http';

export interface Course {
  nom: string;
  nb_etuds : number;
}

@Injectable({
  providedIn: 'root'
})
export class MonServiceService {

  constructor(private http: HttpClient) { }

  getCourses() : Observable<Course[]> {
    return this.http.post<Course[]>(
      'http://127.0.0.1/xmobile_cours/getCourses.php',
      null,
      { withCredentials: true } // capture les cookies de session
    );
  }
}

CORS, CORS, CORS

Ci-dessous, j’ai reporté le fichier PHP que l’on avait vu plus haut pour répondre à la requête de MonServiceService. On peut noter les headers ajoutés sur les lignes en jaune. Ils sont importants. En effet, si vous les supprimez, vous verrez que votre frontend ne pourra plus communiquer avec votre backend. La raison en est le Cross Origin Resource Sharing (CORS). Le problème vient du fait que le backend est sur un serveur Apache (port 80) tandis que le frontend est servi sur un serveur du port 4200. Autrement dit, il faut échanger des données entre 2 serveurs et c’est potentiellement dangereux. Du coup, c’est par défaut interdit. Pour pallier cela, les headers soulignés en jaune indiquent qu’on autorise tout de même ces transferts de données. Lorsque vous déploierez votre forum, le problème ne se posera plus car backend et frontend seront tous les deux sur le même serveur Apache. Il n’apparaît qu’en phase de développement. Vous pourrez noter que le fichier helper.php que vous avez placé dans votre backend dans une séance précédente contient déjà ces headers.

getCourses.php
<?php

// on indique ici que le script va transmettre des données JSON
header('Content-type:application/json;charset=utf8');

// Ici, on ajoute un header pour contourner le problème de CORS.
// Notez que l'on doit le faire, ici, parce que le frontend Angular est servi sur
// le port 4200 alors que le backend PHP est servi par Apache sur le port 80.
header("Access-Control-Allow-Origin: " . $_SERVER['HTTP_ORIGIN']);
header("Access-Control-Allow-Credentials: true");

echo json_encode ([
  [ 'nom' => 'c1', 'nb_etuds' => 2 ],
  [ 'nom' => 'c2', 'nb_etuds' => 5 ],
  [ 'nom' => 'c3', 'nb_etuds' => 6 ]
]);

?>

Exercice 1 : Forum - Le service de messagerie   

Créez un service message, qui sera utilisé pour abstraire la transmission des requêtes http transmises au backend.

Rajoutez à votre classe MessageService une méthode sendMessage qui prend en argument une chaîne de caractères url représentant une URL ainsi qu’un deuxième argument data de type any, qui représente les données du POST à envoyer au backend. Pour l’instant, indiquez que votre fonction renverra une valeur de n’importe quel type (any).

Pour rendre votre programme assez générique, la chaîne url ne va pas contenir l’URL complète à laquelle on essaye d’accéder, mais seulement la partie après le dernier « / », et sans l’extension « .php ». Par exemple, si l’URL complète est :

https://christophe-gonzales.pedaweb.univ-amu.fr/forum/fr-FR/backend/checkLogin.php

le paramètre url ne contiendra que checkLogin. Vous allez sauvegarder toute la partie de l’URL complète avant le dernier « / », autrement dit le préfixe de l’URL complète, dans un attribut de votre classe MessageService. La première opération que doit donc réaliser votre méthode sendMessage consiste à recréer l’URL complète en concaténant le préfixe, l’argument url et l’extension « .php ».

Pour tester votre méthode sendMessage, faites en sorte qu’elle retourne l’URL que vous avez complétée. Faites également en sorte que, sur la page de login, lorsque l’on clique sur le bouton « se connecter », la méthode sendMessage soit exécutée et que ce qu’elle retourne soit affiché dans la console.

Exercice 2 : Forum - Type de retour de sendMessage   

Il faut définir le format des messages qui vont transiter de votre backend vers votre frontend Angular. Pour cela, regardez ce que renvoient les fonctions sendMessage et sendError du fichier helper.php de votre backend. Cela vous indique les champs des objets JSON qui vous seront transmis. Créez dans le fichier message.service.ts une interface PhpData qui représente ce type d’objets JSON.

Exercice 3 : Forum - sendMessage et les données du POST   

Le 2ème argument de la méthode sendMessage de votre classe MessageService, appelons-le data est un objet Javascript/TypeScript (dont le type sera any) contenant tous les paramètres permettant de spécifier à quel user/cours/topic/post, on souhaite accéder. Par exemple, pour authentifier votre utilisateur, data devrait être égal à un objet similaire à :

{ login: 'mon_login', password: 'mon_password' }

Pour transmettre cet objet Javascript à votre backend, il faut le transformer en FormData. Opérez cette transformation, cf. l’url :

https://developer.mozilla.org/fr/docs/Web/API/FormData

Vous devez faire en sorte que cette transformation fonctionne quels que soient les champs de l’objet, pas seulement le login/password.

Indice 1 

Pour être certain(e) que votre transformation est correcte, je vous suggère de regarder les méthodes keys() et values() des FormData et de vous en servir pour afficher dans la console le contenu du FormData que vous avez construit.

Exercice 4 : Forum - Envoi réel de messages   

Modifiez le type de retour de votre méthode sendMessage : c’est maintenant un Observable<PhpData>. Rajoutez à votre méthode les instructions qui envoient les informations du FormData à votre backend par requête http. Modifiez la méthode du LoginComponent associée au bouton de connexion de sorte à ce qu’elle affiche dans la console les données retournées par sendMessage.

Indice 1 

Si tout ne fonctionne pas correctement, cela peut venir du fait que :

  1. votre frontend n’accède pas à la bonne URL.

  2. vous avez un problème de CORS.

  3. le frontend n’a pas transmis correctement les données au $_POST.

  4. même si $_POST est correct, le code de checkLogin.php renvoie un message d’erreur.

Il faut examiner ces points dans l’ordre indiqué ci-dessus.

Concernant le point n°1, dans votre navigateur, au lieu d’afficher la console, affichez l’onglet network (qui apparaissent quand on a cliqué droit et demandé à faire des inspections). Cliquez sur le bouton de connexion et regardez si vous obtenez bien un code 200 pour l’url que vous requêtez. Un code 404 indique que vous vous êtes trompé(e).

Concernant le point n°2, dans la console, si vous voyez apparaître un message « Cross-Origin Request Blocked » ou quelque chose d’approchant, reportez-vous à la section CORS ci-dessus.

Concernant le point n°3, rajoutez au début du script PHP, après avoir importé helper.php, une instruction sendMessage($_POST);. En affichant dans la console les données retournées par le backend, vous verrez si le $_POST est correct ou non. S’il est vide, vous avez probablement un FormData incorrect, qu’il faut donc corriger ou bien vous avez transmis le data passé en argument de sendMessage plutôt que le FormData. Lorsque l’affichage dans la console du $_POST est correct, supprimez la ligne sendMessage($_POST); que vous aviez ajoutée dans checkLogin.php.

Concernant le point n°4, relisez le code de checkLogin.php. Au besoin faites des affichages via la fonction PHP sendMessage() du helper.php pour débugguer.

Exercice 5 : Forum - Logins/passwords incorrects   

Faites en sorte que, si le backend vous indique une erreur de login/password, le message d’erreur soit affiché sur la page web dans une alerte de bootstrap, cf. l’url :

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

 
© C.G. 2007 - 2025