Skip to content

Commit 6295c84

Browse files
committed
Update backend call practical work
1 parent d7d9526 commit 6295c84

File tree

2 files changed

+104
-175
lines changed

2 files changed

+104
-175
lines changed

docs/src/fr/services/README.md

Lines changed: 52 additions & 87 deletions
Original file line numberDiff line numberDiff line change
@@ -166,7 +166,7 @@ import { UserService } from '@services/user.service'
166166
templateUrl: './user.component.html'
167167
})
168168
export class UserComponent {
169-
user: User | null = null
169+
user: User | undefined = undefined
170170
reference = ''
171171

172172
constructor(private userService: UserService) {}
@@ -199,82 +199,48 @@ La commande `npm start` vous demandera une clé d'API. Attendez que votre instru
199199
Le contrat d'interface backend est disponible ici : [api-docs](http://localhost:3030/api-docs)
200200
:::
201201

202-
1. Ajoutez au dossier `src` soit le fichier `proxy.conf.json` si vous n'êtes pas derrière un proxy d'entreprise, soit le fichier `proxy.conf.js`.
202+
1. Ajoutez au dossier `src` le fichier `proxy.conf.json`.
203203

204204
<CodeGroup>
205205
<CodeGroupItem title="proxy.conf.json">
206206

207207
```json
208208
{
209-
"/api/*": {
209+
"/api/**": {
210210
"target": "http://localhost:3030",
211211
"changeOrigin": true,
212212
"pathRewrite": {
213213
"^/api": ""
214214
}
215215
}
216216
}
217-
```
218-
</CodeGroupItem>
219-
<CodeGroupItem title="proxy.conf.js">
220-
221-
```js
222-
var HttpsProxyAgent = require('https-proxy-agent')
223-
var proxyConfig = [{
224-
context: '/api',
225-
target: 'http://localhost:3030',
226-
changeOrigin: true,
227-
pathRewrite: {
228-
"^/api": ""
229-
}
230-
}]
231-
232-
function setupForCorporateProxy(proxyConfig) {
233-
var proxyServer = process.env.http_proxy || process.env.HTTP_PROXY
234-
if (proxyServer) {
235-
var agent = new HttpsProxyAgent(proxyServer);
236-
console.log('Using corporate proxy server: ' + proxyServer)
237-
proxyConfig.forEach(function(entry) {
238-
entry.agent = agent
239-
})
240-
}
241-
return proxyConfig
242-
}
243-
244-
module.exports = setupForCorporateProxy(proxyConfig)
245-
246217
```
247218
</CodeGroupItem>
248219
</CodeGroup>
249220

250-
Le proxy détournera tous les appels à l'URL commençant par http://localhost:4200/api vers le serveur disponible à l'adresse http://localhost:3030. Cela garantit également que nous ne rencontrerons aucun problème lié aux CORS (dans le cas où le backend ne serait hébergé en local). Cette configuration concerne uniquement le serveur de développement webpack fourni par le CLI pour exécuter l'application sur votre machine dans un environnement de développement. Ce ne sera pas la configuration utilisée en production.
221+
Le proxy détournera tous les appels à l'URL commençant par http://localhost:4200/api vers le serveur disponible à l'adresse http://localhost:3030. Cela garantit également que nous ne rencontrerons aucun problème lié aux CORS (dans le cas où le backend ne serait hébergé en local). Cette configuration concerne uniquement le serveur de développement webpack ou vite fourni par le CLI pour exécuter l'application sur votre machine dans un environnement de développement. Ce ne sera pas la configuration utilisée en production.
251222

252-
2. Installez la dépendance suivante uniquement si vous êtes derrière un proxy d'entreprise
253-
```sh
254-
npm install --save-dev https-proxy-agent
255-
```
256223

257-
3. Dans le fichier de configuration CLI - `angular.json` - ajoutez l'option `proxyConfig` à la target serve :
224+
2. Dans le fichier de configuration CLI - `angular.json` - ajoutez l'option `proxyConfig` à la target serve :
258225

259226
```json{5,6,7}
260227
...
261228
"serve": {
262229
"builder": "@angular-devkit/build-angular:dev-server",
263230
...
264231
"options": {
265-
"proxyConfig": "src/proxy.conf.json" // or "src/proxy.conf.js"
232+
"proxyConfig": "src/proxy.conf.json"
266233
},
267234
"defaultConfiguration": "development"
268235
},
269236
...
270237
```
271238

272-
4. Ajoutez le `HttpClientModule` au tableau `imports` de `AppModule`. Si VSCode ne parvient pas à trouver l'importation, ajoutez la ligne suivante manuellement en haut du fichier `app.module.ts` :
273-
```ts
274-
import { HttpClientModule } from '@angular/common/http'
275-
```
239+
Redémarrez le projet comme la configuration du CLI a changé (`angular.json`).
276240

277-
5. Créez les interfaces/classes pour les modèles utilisés par le backend, ajoutez un fichier par modèle dans le dossier `models/authentication` :
241+
3. Ajoutez `provideHttpClient()` au tableau de `providers` de l'object `appConfig` dans le fichier `app.config.ts`.
242+
243+
4. Créez les interfaces/classes pour les modèles utilisés par le backend, ajoutez un fichier par modèle dans le dossier `models/authentication` :
278244

279245
<CodeGroup>
280246
<CodeGroupItem title="registration-request.ts">
@@ -334,7 +300,7 @@ export class User {
334300

335301
Prenez note du token dans la `UserResponse`, il servira à authentifier l'utilisateur via l'entête Authorization : `Authorization: Bearer <token>`. Apprenez-en plus sur les JWT [ici](https://jwt.io/introduction).
336302

337-
6. Implémentez les méthodes `register` et `login` dans `AuthenticationService` comme suit :
303+
5. Implémentez les méthodes `register` et `login` dans `AuthenticationService` comme suit :
338304

339305
<CodeGroup>
340306
<CodeGroupItem title="authentication.service.ts">
@@ -362,23 +328,23 @@ register(loginRequest: LoginRequest): Observable<UserResponse> {
362328
</CodeGroupItem>
363329
</CodeGroup>
364330

365-
7. La modification de la signature d'appel de la méthode `login` va nécessiter un peu de refactorisation dans le `LoginFormComponent` :
331+
6. La modification de la signature d'appel de la méthode `login` va nécessiter un peu de refactorisation dans le `LoginFormComponent` :
366332

367333
<CodeGroup>
368334
<CodeGroupItem title="login-form.component.ts">
369335

370336
```ts
371337
constructor(
372338
private router: Router,
373-
private route: ActivatedRoute,
339+
private activatedRoute: ActivatedRoute,
374340
private authenticationService: AuthenticationService
375341
) {}
376342

377343
login(): void {
378344
this.authenticationService.login(this.loginRequest)
379345
.subscribe({ next: () => {
380-
const returnUrl = this.route.snapshot.queryParamMap.get('returnUrl')
381-
this.router.navigateByUrl(returnUrl ? `/${returnUrl}` : '')
346+
const postLoginRoute = this.activatedRoute.snapshot.queryParamMap.get('returnUrl')
347+
this.router.navigateByUrl(postLoginRoute ? `/${postLoginRoute}` : '')
382348
} })
383349
}
384350

@@ -394,7 +360,8 @@ get loginRequest(): LoginRequest {
394360
</CodeGroupItem>
395361
</CodeGroup>
396362

397-
8. Une refactorisation est également nécessaire pour que `AuthenticationGuard` continue de fonctionner. Faites en sorte que le booléen `loggedIn` dans `AuthenticationService` dépende d'un champ `token` et faites en sorte que le `LoginFormComponent` sauvegarde le token qu'il obtient de l'appel de connexion dans ce champ.
363+
7. Une refactorisation est également nécessaire pour que l'`authenticationGuard` continue de fonctionner. Faites en sorte que le booléen `loggedIn` dans `AuthenticationService` dépende d'un champ `token` et faites en sorte que le `LoginFormComponent` sauvegarde le token qu'il obtient de l'appel de connexion dans ce champ.
364+
Refactorez aussi la méthode `logout()` pour qu'elle remette le `token` à null.
398365

399366
<CodeGroup>
400367
<CodeGroupItem title="authentication.service.ts">
@@ -410,20 +377,22 @@ get loggedIn(): boolean {
410377

411378
<CodeGroupItem title="login-form.component.ts">
412379

413-
```ts{4}
380+
```ts{3, 4}
414381
login(): void {
415382
this.authenticationService.login(this.loginRequest)
416-
.subscribe(response => {
383+
.subscribe({ next: response => {
417384
this.authenticationService.token = response.token
418-
const returnUrl = this.route.snapshot.paramMap.get('returnUrl')
419-
this.router.navigateByUrl(returnUrl ? `/${returnUrl}` : '')
420-
})
385+
const postLoginUrl = this.activatedRoute.snapshot.queryParamMap.get('returnUrl')
386+
this.router.navigateByUrl(postLoginUrl ? `/${postLoginUrl}` : '')
387+
} })
421388
}
422389
```
423390
</CodeGroupItem>
424391
</CodeGroup>
425392

426-
9. Ajoutez un bouton d'enregistrement à côté du bouton de connexion dans le `LoginFormComponent`, donnez-lui l'attribut `type="button"` afin qu'Angular sache que ce n'est pas ce bouton qui déclenche l'événement `ngSubmit` sur le formulaire et faites-lui appeler le méthode d'enregistrement. Vous devriez maintenant pouvoir enregistrer un utilisateur et vous connecter.
393+
8. Ajoutez un bouton d'enregistrement à côté du bouton de connexion dans le `LoginFormComponent`, donnez-lui l'attribut `type="button"` afin qu'Angular sache que ce n'est pas ce bouton qui déclenche l'événement `ngSubmit` sur le formulaire et faites-lui appeler le méthode d'enrôlement.
394+
Vous devriez maintenant pouvoir enregistrer un utilisateur et vous connecter.
395+
Si l'enrôlement semble ne pas fonctionner, vérifiez l'onglet network des outils de développement de votre navigateur (onglet preview de l'appel réseau en erreur), votre mail ou mot de passe ne respectent peut-être pas les règles de validation du back.
427396

428397
<CodeGroup>
429398
<CodeGroupItem title="HTML">
@@ -446,80 +415,76 @@ login(): void {
446415
</CodeGroupItem>
447416
</CodeGroup>
448417

449-
10. Il est temps de gérer les erreurs. La méthode subscribe peut prendre un objet qui propose trois callbacks: une *next*, une *error* et une *complete* (nous verrons cela plus en détail dans le chapitre suivant). Déclarer un champ `errorMessage` sur le `LoginFormComponent` et le mettre à jour en vous servant de l'argument renvoyé par la callback `error`. Afficher le message d'erreur sur le formulaire. Vérifier que le message d'erreur s'affiche bien lorsqu'on saisit des identifiants incorrects.
418+
9. Il est temps de gérer les erreurs. La méthode subscribe peut prendre un objet qui propose trois callbacks: une *next*, une *error* et une *complete* (nous verrons cela plus en détail dans le chapitre suivant). Déclarer un champ `errorMessage` sur le `LoginFormComponent` et le mettre à jour en vous servant de l'argument renvoyé par la callback `error`. Afficher le message d'erreur sur le formulaire. Vérifier que le message d'erreur s'affiche bien lorsqu'on saisit des identifiants incorrects.
419+
420+
<CodeGroup>
421+
<CodeGroupItem title="login-form.component.ts">
450422

451423
```ts
424+
errorMessage = ''
425+
452426
private errorHandler(errorResponse: HttpErrorResponse): void {
453427
this.errorMessage = errorResponse.error.error ?? `${errorResponse.error.status} - ${errorResponse.error.statusText}`
454428
}
455429

456430
// subscribe syntax
457431
this.authenticationService.login(this.loginRequest)
458432
.subscribe({
459-
next: (userResponse) => { /* */},
460-
error: (errorResponse) => { /* */ }
433+
next: response => { /* */},
434+
error: errorResponse => { /* */ }
461435
})
462436
```
437+
</CodeGroupItem>
438+
</CodeGroup>
463439

464440
::: tip hint
465441
Pour une meilleure UX (User eXperience), penser à vider le champ `errorMessage` soit avant de lancer une nouvelle requête d'authentification ou d'enregistrement, soit dès que celles-ci se terminent en succès.
466442
:::
467443

468-
11. Appelons maintenant le backend pour obtenir la liste des films. La route est sécurisée, ce qui signifie que le passage du token dans l'en-tête est nécessaire. Angular fournit un mécanisme - les intercepteurs http - pour intercepter systématiquement les requêtes http, permettant de définir les en-têtes en un seul endroit.
444+
10. Appelons maintenant le backend pour obtenir la liste des films. La route est sécurisée, ce qui signifie que le passage du token dans l'en-tête est nécessaire. Angular fournit un mécanisme - les intercepteurs http - pour intercepter systématiquement les requêtes http, permettant de définir les en-têtes en un seul endroit.
469445

470446
a. Utilisez le CLI pour en générer un : `ng generate interceptor interceptors/authentication`.
471447

472448
b. Voici son implémentation :
473449

474450
```ts
475-
import { Injectable } from '@angular/core'
476-
import { HttpRequest, HttpHandler, HttpEvent, HttpInterceptor } from '@angular/common/http'
477-
import { Observable } from 'rxjs'
451+
import { HttpInterceptorFn } from '@angular/common/http'
478452
import { AuthenticationService } from '@services/authentication.service'
453+
import { inject } from '@angular/core'
479454

480-
@Injectable()
481-
export class AuthenticationInterceptor implements HttpInterceptor {
482-
483-
constructor(private authenticationService: AuthenticationService) {}
484-
485-
intercept(request: HttpRequest<unknown>, next: HttpHandler): Observable<HttpEvent<unknown>> {
486-
const token = this.authenticationService.token
487-
if (token) {
488-
const cloned = request.clone({
489-
headers: request.headers.set('Authorization', `Bearer ${this.authenticationService.token}`)
490-
})
491-
492-
return next.handle(cloned)
493-
}
455+
export const authenticationInterceptor: HttpInterceptorFn = (req, next) => {
456+
const token = inject(AuthenticationService).token
457+
if (token) {
458+
const cloned = req.clone({
459+
headers: req.headers.set('Authorization', `Bearer ${token}`)
460+
})
494461

495-
return next.handle(request)
462+
return next(cloned)
496463
}
464+
465+
return next(req)
497466
}
498467
```
499468

500469
S'il y a un token dans l'`AuthenticationService`, l'intercepteur l'ajoutera aux en-têtes de la requête http.
501470

502-
c. Ajouter l'intercepteur aux providers de l'`AppModule`
471+
c. Ajouter l'intercepteur au provider de l'`HttpClient` de l'objet `appConfig`:
503472

504473
```ts
505-
providers: [
506-
{ provide: HTTP_INTERCEPTORS, useClass: AuthenticationInterceptor, multi: true }
507-
],
474+
provideHttpClient(withInterceptors([authenticationInterceptor]))
508475
```
509476

510-
12. Créez un `FilmService` à l'aide du CLI et implémentez l'appel au endpoint `api/movies/search`. Notez que le queryParam `title` n'est pas facultatif. Pour ajouter des query params à une requête, utilisez le paramètre `options` de la méthode get.
477+
11. Créez un `FilmService` à l'aide du CLI et implémentez l'appel au endpoint `api/movies/search`. Notez que le queryParam `title` n'est pas facultatif. Pour ajouter des query params à une requête, utilisez le paramètre `options` de la méthode get.
511478

512479
```ts
513480
const options = {
514481
params: new HttpParams().set('title', title)
515482
}
516483
```
517484

518-
13. Apportez des modifications au `FilmSearchComponent` pour appeler ce nouveau service avec le titre renseigné par l'utilisateur, enregistrez la réponse dans le champ `films` du `FilmSearchComponent`.
519-
520-
14. Vérifiez que le token est envoyé sous forme d'en-tête HTTP via les outils de développement de votre navigateur.
485+
12. Apportez des modifications au `FilmSearchComponent` pour appeler ce nouveau service avec le titre renseigné par l'utilisateur, enregistrez la réponse dans le champ `films` du `FilmSearchComponent`.
521486

522-
15. **Bonus :** Modifiez la méthode de déconnexion `AuthenticationService` pour qu'elle passe le token à `null`.
487+
13. Vérifiez que le token est envoyé sous forme d'en-tête HTTP via les outils de développement de votre navigateur.
523488

524489
::: details Résultat attendu
525490
![Résultat visuel du TP http](../../assets/visual-7a.png)

0 commit comments

Comments
 (0)