Skip to content

Commit 1c5eb70

Browse files
committed
Update httpClient part of the service chapter
1 parent 606614f commit 1c5eb70

File tree

2 files changed

+44
-64
lines changed

2 files changed

+44
-64
lines changed

docs/src/en/services.md

Lines changed: 22 additions & 32 deletions
Original file line numberDiff line numberDiff line change
@@ -59,9 +59,9 @@ Dependencies can be provided at three levels:
5959

6060
## Practical Work: State management
6161
1. Generate an `AuthenticationService` with the CLI in the `app/services` folder
62-
2. Move the `loggedIn` logic from the `AppComponent` to the service
62+
2. Move the `loggedIn` logic from the `App` component to the service
6363
3. Inject the service in the `LoginFormComponent` and use it.
64-
4. Implement a logout method in the authentication service and add a logout button in the `AppComponent` that calls it and navigates back to the `LoginFormComponent`. Here is the html and css:
64+
4. Implement a logout method in the authentication service and add a logout button in the `App` component that calls it and navigates back to the `LoginFormComponent`. Here is the html and css:
6565

6666
<CodeGroup>
6767
<CodeGroupItem title="app.html">
@@ -154,7 +154,7 @@ export class UserService {
154154
<CodeGroupItem title="Component">
155155

156156
```ts
157-
import { Component, inject } from '@angular/core'
157+
import { Component, inject, signal } from '@angular/core'
158158
import { User } from 'app/models/user/user'
159159
import { UserService } from 'app/services/user.service'
160160

@@ -165,12 +165,12 @@ import { UserService } from 'app/services/user.service'
165165
export class UserComponent {
166166
private readonly userService = inject(UserService)
167167

168-
protected user: User | undefined = undefined
168+
protected readonly user = signal<User | undefined>(undefined)
169169
protected reference = ''
170170

171171
getUser(): void {
172172
this.userService.getByUserReference(this.reference))
173-
.subscribe(user => this.user = user)
173+
.subscribe(user => this.user.set(user))
174174
}
175175
}
176176
```
@@ -222,12 +222,13 @@ The proxy will divert all calls for http://localhost:4200/api to the server runn
222222
```json{5,6,7}
223223
...
224224
"serve": {
225-
"builder": "@angular-devkit/build-angular:dev-server",
226-
...
225+
"builder": "@angular/build:dev-server",
227226
"options": {
228227
"proxyConfig": "src/proxy.conf.json"
229228
},
230-
"defaultConfiguration": "development"
229+
"configurations": {
230+
...
231+
}
231232
},
232233
...
233234
```
@@ -304,9 +305,11 @@ Note the token in the `UserResponse`, it will serve to authenticate the user via
304305
```ts
305306
private readonly httpClient = inject(HttpClient)
306307
private readonly baseUrl = 'api/user'
308+
private readonly token = signal<string | null>(null)
307309

308310
login(loginRequest: LoginRequest): Observable<UserResponse> {
309311
return this.httpClient.post<UserResponse>(`${this.baseUrl}/login`, loginRequest)
312+
.pipe(tap(response => this.token.set(response.token)))
310313
}
311314

312315
register(loginRequest: LoginRequest): Observable<UserResponse> {
@@ -352,37 +355,21 @@ register(): void {
352355
</CodeGroupItem>
353356
</CodeGroup>
354357

355-
7. Refactoring is also needed to keep the `authenticationGuard` working. Make the `loggedIn` boolean in the `AuthenticationService` depend on a `token` field and make the `LoginFormComponent` save the token that it gets from the login call in that field.
356-
You will also need to refactor the logout to empty the `token`.
358+
7. Refactoring is also needed to keep the `authenticationGuard` working. Make the `loggedIn` variable in the `AuthenticationService` a computed signal that depends on the `token` signal.
359+
You will also need to refactor the logout to empty the `token` signal value.
357360

358361
<CodeGroup>
359362
<CodeGroupItem title="authentication.service.ts">
360363

361364
```ts
362-
readonly token: WritableSignal<string | null> = signal(null)
363-
364365
readonly loggedIn = computed(() => this.token() !== null)
365366
```
366367
</CodeGroupItem>
367-
368-
<CodeGroupItem title="login-form.component.ts">
369-
370-
```ts{3, 4}
371-
login(): void {
372-
this.authenticationService.login(this.loginRequest())
373-
.subscribe({ next: response => {
374-
this.authenticationService.token.set(response.token)
375-
const postLoginUrl = this.activatedRoute.snapshot.queryParamMap.get('returnUrl')
376-
this.router.navigateByUrl(postLoginUrl ? `/${postLoginUrl}` : '')
377-
} })
378-
}
379-
```
380-
</CodeGroupItem>
381368
</CodeGroup>
382369

383370
8. Add a register button next to the login button in the `LoginFormComponent`, give it the attribute `type="button"` so that Angular knows it is not this button that triggers the `ngSubmit` event on the form and make it call the register method.
384371
You should now be able to register a user and login.
385-
If you are having trouble, check the errors in the network tab of the developer tools (preview tab of the network call in error), your email or password may not comply with the policy.
372+
If you are having trouble, **check the errors in the network tab** of the developer tools (preview tab of the network call in error), your email or password may not comply with the policy.
386373

387374
<CodeGroup>
388375
<CodeGroupItem title="HTML">
@@ -405,7 +392,7 @@ If you are having trouble, check the errors in the network tab of the developer
405392
</CodeGroupItem>
406393
</CodeGroup>
407394

408-
9. It is time to handle errors. The subscribe method can be passed an object that takes three callbacks: a *next*, an *error* and a *complete* (we will look at this in more details in the next chapter). Declare an `errorMessage` field on the `LoginFormComponent` and update it with the error information retrieved from the argument of the `error` callback. Display the error message on the form. Check that the error message is actually shown when you login with incorrect credentials.
395+
9. It is time to handle errors. The subscribe method can be passed an object that takes three callbacks: a *next*, an *error* and a *complete* (we will look at this in more details in the next chapter). Declare an `errorMessage` signal in the `LoginFormComponent` class and set it with the error information retrieved from the argument of the `error` callback. Display the error message on the form. Check that the error message is actually shown when you login with incorrect credentials.
409396

410397
<CodeGroup>
411398
<CodeGroupItem title="login-form.component.ts">
@@ -420,8 +407,8 @@ private errorHandler(errorResponse: HttpErrorResponse): void {
420407
// subscribe syntax
421408
this.authenticationService.login(this.loginRequest())
422409
.subscribe({
423-
next: response => { /* */},
424-
error: errorResponse => { /* */ }
410+
next: response => { /* insert code here */},
411+
error: errorResponse => { /* insert code here */ }
425412
})
426413
```
427414
</CodeGroupItem>
@@ -472,12 +459,15 @@ const options = {
472459
}
473460
```
474461

475-
12. Make changes to the `FilmSearchComponent` to call this new service with the title filled in by the user, save the response to the `films` field in the `FilmSearchComponent`.
462+
12. Make changes to the `FilmSearchComponent` to call this new service with the title filled in by the user, save the response to the `films` signal field in the `FilmSearchComponent` class.
476463

477464
13. Check that the token is sent as a HTTP header via the developer tools of your browser.
478465

479466
::: details Expected result
480467
![Visual result of the http practical work](../assets/visual-7a.png)
481468

482469
![Visual result of the http practical work](../assets/visual-7b.png)
483-
:::
470+
:::
471+
472+
## To go further: HttpResource
473+
The `HttpClient` makes use of Observables from the `rxjs` library. Having two reactivity models (Observables and signals) competing in the same framework may seem strange. Observables have been around since the first version of Angular and are mainly encountered whilst making http calls or interacting with reactive forms, they were not necessary everywhere thanks to `zone.js`. They shine in complex reactive use cases that don't form the bulk of an Angular application code base but that are essential to the proper running of your application. Signals won't take over the role of Observables as they offer a less powerful API, however they will replace Observables in many simpler cases. There's a signal-based wrapper for the `HttpClient` called `HttpResource` that was introduced in Angular 19.2 as experimental. You can explore its capabilities [here](https://angular.dev/guide/http/http-resource). It is advised to only use it for GET requests.

docs/src/fr/services.md

Lines changed: 22 additions & 32 deletions
Original file line numberDiff line numberDiff line change
@@ -59,9 +59,9 @@ Les dépendances peuvent être fournies à trois niveaux :
5959

6060
## TP : Gestion de l'État
6161
1. Générez un `AuthenticationService` avec le CLI dans le dossier `app/services`
62-
2. Déplacez la logique du `loggedIn` de l'`AppComponent` vers le service
62+
2. Déplacez la logique du `loggedIn` du composant `App` vers le service
6363
3. Injectez le service dans le `LoginFormComponent` et utilisez-le.
64-
4. Implémentez une méthode de déconnexion dans le service d'authentification et ajoutez un bouton de déconnexion dans l'`AppComponent` qui l'appelle et provoque une navigation vers le `LoginFormComponent`. Voici l'html et le css :
64+
4. Implémentez une méthode de déconnexion dans le service d'authentification et ajoutez un bouton de déconnexion dans le composant `App` qui l'appelle et provoque une navigation vers le `LoginFormComponent`. Voici l'html et le css :
6565

6666
<CodeGroup>
6767
<CodeGroupItem title="app.html">
@@ -154,7 +154,7 @@ export class UserService {
154154
<CodeGroupItem title="Component">
155155

156156
```ts
157-
import { Component, inject } from '@angular/core'
157+
import { Component, inject, signal } from '@angular/core'
158158
import { User } from 'app/models/user/user'
159159
import { UserService } from 'app/services/user.service'
160160

@@ -165,12 +165,12 @@ import { UserService } from 'app/services/user.service'
165165
export class UserComponent {
166166
private readonly userService = inject(UserService)
167167

168-
protected user: User | undefined = undefined
168+
protected readonly user = signal<User | undefined>(undefined)
169169
protected reference = ''
170170

171171
getUser(): void {
172172
this.userService.getByUserReference(this.reference))
173-
.subscribe(user => this.user = user)
173+
.subscribe(user => this.user.set(user))
174174
}
175175
}
176176
```
@@ -223,12 +223,13 @@ Le proxy détournera tous les appels à l'URL commençant par http://localhost:4
223223
```json{5,6,7}
224224
...
225225
"serve": {
226-
"builder": "@angular-devkit/build-angular:dev-server",
227-
...
226+
"builder": "@angular/build:dev-server",
228227
"options": {
229228
"proxyConfig": "src/proxy.conf.json"
230229
},
231-
"defaultConfiguration": "development"
230+
"configurations": {
231+
...
232+
}
232233
},
233234
...
234235
```
@@ -305,9 +306,11 @@ Prenez note du token dans la `UserResponse`, il servira à authentifier l'utilis
305306
```ts
306307
private readonly httpClient = inject(HttpClient)
307308
private readonly baseUrl = 'api/user'
309+
private readonly token = signal<string | null>(null)
308310

309311
login(loginRequest: LoginRequest): Observable<UserResponse> {
310312
return this.httpClient.post<UserResponse>(`${this.baseUrl}/login`, loginRequest)
313+
.pipe(tap(response => this.token.set(response.token)))
311314
}
312315

313316
register(loginRequest: LoginRequest): Observable<UserResponse> {
@@ -353,37 +356,21 @@ register(): void {
353356
</CodeGroupItem>
354357
</CodeGroup>
355358

356-
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.
357-
Refactorez aussi la méthode `logout()` pour qu'elle remette le `token` à null.
359+
7. Une refactorisation est également nécessaire pour que l'`authenticationGuard` continue de fonctionner. Transformez la variable `loggedIn` dans l'`AuthenticationService` en signal computed qui dépend du champ `token`.
360+
Refactorez aussi la méthode `logout()` pour qu'elle remette la valeur du signal `token` à null.
358361

359362
<CodeGroup>
360363
<CodeGroupItem title="authentication.service.ts">
361364

362365
```ts
363-
readonly token: WritableSignal<string | null> = signal(null)
364-
365366
readonly loggedIn = computed(() => this.token() !== null)
366367
```
367368
</CodeGroupItem>
368-
369-
<CodeGroupItem title="login-form.component.ts">
370-
371-
```ts{3, 4}
372-
login(): void {
373-
this.authenticationService.login(this.loginRequest())
374-
.subscribe({ next: response => {
375-
this.authenticationService.token.set(response.token)
376-
const postLoginUrl = this.activatedRoute.snapshot.queryParamMap.get('returnUrl')
377-
this.router.navigateByUrl(postLoginUrl ? `/${postLoginUrl}` : '')
378-
} })
379-
}
380-
```
381-
</CodeGroupItem>
382369
</CodeGroup>
383370

384371
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.
385372
Vous devriez maintenant pouvoir enregistrer un utilisateur et vous connecter.
386-
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.
373+
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.
387374

388375
<CodeGroup>
389376
<CodeGroupItem title="HTML">
@@ -406,7 +393,7 @@ Si l'enrôlement semble ne pas fonctionner, vérifiez l'onglet network des outil
406393
</CodeGroupItem>
407394
</CodeGroup>
408395

409-
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.
396+
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 signal `errorMessage` dans la classe `LoginFormComponent` et changer sa valeur 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.
410397

411398
<CodeGroup>
412399
<CodeGroupItem title="login-form.component.ts">
@@ -421,8 +408,8 @@ private errorHandler(errorResponse: HttpErrorResponse): void {
421408
// subscribe syntax
422409
this.authenticationService.login(this.loginRequest())
423410
.subscribe({
424-
next: response => { /* */},
425-
error: errorResponse => { /* */ }
411+
next: response => { /* insérer du code ici */},
412+
error: errorResponse => { /* insérer du code ici */ }
426413
})
427414
```
428415
</CodeGroupItem>
@@ -473,12 +460,15 @@ const options = {
473460
}
474461
```
475462

476-
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`.
463+
12. Apportez des modifications au `FilmSearchComponent` pour appeler ce nouveau service avec le titre renseigné par l'utilisateur, enregistrez la réponse dans le signal `films` de la classe `FilmSearchComponent`.
477464

478465
13. Vérifiez que le token est envoyé sous forme d'en-tête HTTP via les outils de développement de votre navigateur.
479466

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

483470
![Résultat visuel du TP http](../assets/visual-7b.png)
484-
:::
471+
:::
472+
473+
## Aller plus loin : HttpResource
474+
L'`HttpClient` utilise les Observables de la librairie `rxjs`. Avoir deux modèles de réactivité (les Observables et les signaux) qui coexistent au sein du même framework peut sembler étrange. Les Observables sont présents depuis la première version d'Angular et sont principalement rencontrés lors d'appels réseaux ou d'interactions avec les formulaires réactifs. Ils excellent dans des cas d'utilisation réactifs complexes qui ne constituent pas la majeure partie de la base de code d'une application Angular, mais qui sont essentiels au bon fonctionnement de votre application. Les signaux ne prendront pas le rôle des Observables car ils offrent une API moins puissante, toutefois ils remplaceront les Observables dans de nombreux cas plus simples. Un wrapper du `HttpClient` basé sur les signaux a été introduit en expérimental en Angular 19.2, le `HttpResource`. Vous pouvez explorer ses capacités [ici](https://angular.dev/guide/http/http-resource). Il est conseillé de ne l’utiliser que pour les requêtes GET.

0 commit comments

Comments
 (0)