TEST1 - Tutoriel sur une introduction au framework web Angular

iner dukoid

Pour réagir au contenu de ce tutoriel, un espace de dialogue vous est proposé sur le forum. Commentez

- Titre : TEST1 - Tutoriel sur une introduction au framework web Angular

- Auteur : iner dukoid

- Parution : 4 janvier 2021

- Mise à jour : 4 janvier 2021

- Licence : Les sources présentées sur cette page sont libres de droits et vous pouvez les utiliser à votre convenance. Par contre, la page de présentation constitue une œuvre intellectuelle protégée par les droits d'auteur. Copyright ® 2020 iner dukoid. Aucune reproduction, même partielle, ne peut être faite de ce site et de l'ensemble de son contenu : textes, documents, images, etc. sans l'autorisation expresse de l'auteur. Sinon vous encourez selon la loi jusqu'à trois ans de prison et jusqu'à 300 000 € de dommages et intérêts.

Sommaire

- I. Étude de cas 1 : La réutisabilité des composants Web

- I-A. à savoir

- I-B. Description

- I-B-1. Remarques

- I-B-2. Inventaires

- I-C. Pratique

- I-D. Résultat

- I-E. Conclusion

- II. Mise en production : Firebase hosting

- II-A. Pratique

- II-B. à savoir

- II-C. Résultat

I - Étude de cas 1 : La réutisabilité des composants Web

Nous avons vu que réutiliser les composants Web était une bonne pratique car cela améliore la maintenabilité et augmente la productivité.

- nous allons donc dans ce chapitre écrire un exemple de composant Web réutilisable ;

- ce composant affichera une liste d'élements et renverra la choix que l'utilisateur à selectionné, un peu comme un groupe de radio bouton.

I-A - à savoir

- pour une réutisabilité optimale, le composant doit se contenter de ne faire que sa propre fonctionnalité, celui d'afficher une liste et retourner le choix de l'utilisateur ;

- en somme, ce composant :

- ne devra pas contenir la liste des éléments à afficher ;

- ne devra pas aller chercher la liste des éléments à afficher (mais par contre, il le recevra)

I-B - Description

- le composant réutilisable sera contenu dans le dossier : /shared

- pour le design, le composant utilisera un composant UI d'Angular Material ;

- voici le détail du composant Web réutilisable :

- réceptionne la liste des éléments (d'un certain type) à afficher ;

- réceptionne l'élément par défaut qui doit être sélectionné (possible qu'il n'y ai aucun élément) ;

- réceptionne le nom du groupe auquel appartient la liste ;

- sélectionne l'item par défaut ou celui enregistré (lors d'une précédente sélection utilisateur) ;

- renvoi le choix de l'utilisateur au composant qui a fait appel à ce composant ;

- contient le type de donnée que le composant manipule. Ainsi de l'extérieur, on sait de quel type doivent être les données que l'on doit fournir au composant ;

- enregistre le choix de l'utilisateur ;

- le design du composant sera géré par Angular Material.

I-B-1 - Remarques

- pour le design, on va utiliser le toggle button d'Angular material ;

- https://material.angular.io/components/button-toggle/overview

- lors du routing à chaque accès à une page : page1 ou page2, les composants pages s'initialisent et donc ne conserve pas l'état de la sélection. C'est pour cela qu'on utilise un service pour stocker le choix de l'utilisateur et ainsi l'utiliser pour initialiser le composant avec la bonne sélection.

I-B-2 - Inventaires

1. - /shared/button-toggle-mat

2. - /components/button-toggle-mat.component.ts              // le composant

3. - /services/stored.service.ts                             // pour enregistrer le choix de l'utilisateur

4. - /models/i-item-btm.ts                                   // le type de donnée que le composant manipule

5. - button-toggle-mat.module.ts                             // déclare le composant et le service. De plus, exporte le composant

7. - /pages

8. - /page1                                      // composant avec une liste d'item 'quelconque' ayant comme nom de groupe : 'choice'

9. - /page2                                      // composant avec une liste d'item 'couleur' ayant comme nom de groupe : 'color'

10. // composant avec une liste d'item 'quelconque' ayant comme nom de groupe : 'choice'

I-C - Pratique

Créer un nouveau projet : angular-re-use1

1. ng new angular-re-use1

2. routing ? YES

3. SCSS

5. ng add @angular/material

7. ng g m pages --module=app

8. ng g m pages/page1 --module=pages

9. ng g m pages/page2 --module=pages

10. ng g c pages/page1 --module=page1

11. ng g c pages/page2 --module=page2

13. ng g m shared/button-toggle-mat --module=app

14. ng g c shared/button-toggle-mat/components/button-toggle-mat --module=button-toggle-mat

15. ng g i shared/button-toggle-mat/models/i-item-btm

16. ng g s shared/button-toggle-mat/services/stored

app.module.ts

1. import { BrowserModule } from '@angular/platform-browser';

2. import { NgModule } from '@angular/core';

3. import { AppRoutingModule } from './app-routing.module';

4. import { AppComponent } from './app.component';

5. import { NoopAnimationsModule } from '@angular/platform-browser/animations';

6. //

7. import { PagesModule } from './pages/pages.module';

9. @NgModule({

10. declarations: [

11. AppComponent,

12. ],

13. imports: [

14. BrowserModule,

15. AppRoutingModule,

16. NoopAnimationsModule,

17. //

18. PagesModule,                // pour le routing

19. ],

20. providers: [],

21. bootstrap: [AppComponent]

22. })

23. export class AppModule { }

app-routing.module.ts

1. import { NgModule } from '@angular/core';

2. import { Routes, RouterModule } from '@angular/router';

3. //

4. import { Page1Component } from 'src/app/pages/page1/page1.component';

5. import { Page2Component } from 'src/app/pages/page2/page2.component';

7. const routes: Routes = [

8. { path: 'page1', component: Page1Component },

9. { path: 'page2', component: Page2Component },

10. ];

12. @NgModule({

13. imports: [RouterModule.forRoot(routes)],

14. exports: [RouterModule]

15. })

16. export class AppRoutingModule { }

app.component.html

1. <ul>

2. <li><a [routerLink]="['/page1']">aller à page1</a></li>

3. <li><a [routerLink]="['/page2']">aller à page2</a></li>

4. </ul>

6. <router-outlet></router-outlet>

\data\param.ts

1. export const ItemsChoice = [

2. {key: 'K0', value: 'Aucun'},

3. {key: 'K1', value: 'Choix 1'},

4. {key: 'K2', value: 'Choix 2'},

5. {key: 'K3', value: 'Choix 3'},

6. ];

8. export const ItemsColor = [

9. {key: 'NONE', value: 'Aucune'},

10. {key: 'V', value: 'Vert'},

11. {key: 'R', value: 'Rouge'},

12. ];

\pages\page1\page1.module.ts

1. import { NgModule } from '@angular/core';

2. import { CommonModule } from '@angular/common';

3. //

4. import { ButtonToggleMatModule } from '../../shared/button-toggle-mat/button-toggle-mat.module';

5. import { Page1Component } from './page1.component';

8. @NgModule({

9. declarations: [Page1Component],

10. imports: [

11. CommonModule,

12. //

13. ButtonToggleMatModule,            // utilise le composant réutilisable du module

14. ]

15. })

16. export class Page1Module { }

\pages\page2\page2.module.ts

1. import { NgModule } from '@angular/core';

2. import { CommonModule } from '@angular/common';

3. //

4. import { ButtonToggleMatModule } from '../../shared/button-toggle-mat/button-toggle-mat.module';

5. import { Page2Component } from './page2.component';

8. @NgModule({

9. declarations: [Page2Component],

10. imports: [

11. CommonModule,

12. //

13. ButtonToggleMatModule,            // utilise le composant réutilisable du module

15. ]

16. })

17. export class Page2Module { }

\pages\pages.module.ts

1. import { NgModule } from '@angular/core';

2. import { CommonModule } from '@angular/common';

3. //

4. import { Page1Module } from './page1/page1.module';

5. import { Page2Module } from './page2/page2.module';

7. @NgModule({

8. declarations: [],

9. imports: [

10. CommonModule,

11. //

12. Page1Module,

13. Page2Module,

15. ]

16. })

17. export class PagesModule { }

\pages\page1\page1.component.html

1. <p>page1 works!</p>

3. html:ef648c4b35586de068ddd565a237c04bc83f9409

5. <app-button-toggle-mat

6. [items]="choices"

7. [selectedItem]="selectedChoice"

8. [group]="'choice'"

9. (selectedItemEvent)="onSelectedChoice($event)"

10. ></app-button-toggle-mat>

12. (1) choice={{selectedChoice|json}}

\pages\page1\page1.component.ts

1. import { Component, OnInit } from '@angular/core';

2. import { IItemBtm } from '../../shared/button-toggle-mat/models/i-item-btm';

3. import { ItemsChoice } from '../../data/param';

5. @Component({

6. selector: 'app-page1',

7. templateUrl: './page1.component.html',

8. styleUrls: ['./page1.component.scss']

9. })

10. export class Page1Component implements OnInit {

11. choices: IItemBtm[];                // on éxige que ces éléments du tableau doivent être du type : IItemBtm

12. // ainsi, il ne peut y avoir d'erreur car notre composant ne gère que ce type de donnée : IItemBtm

13. selectedChoice: IItemBtm;           // on fourni l'élément par défaut au composant réutilisable (non obligatoire)  (2)

14. // et en même temps va contenir le choix de l'utilisateur

16. constructor() {

17. }

19. ngOnInit(): void {

20. this.choices = ItemsChoice;                // on fourni une liste d'éléments au composant réutilisable

21. this.selectedChoice = this.choices[0];     // (2) par défaut, l'élément qui sera sélectionné sera le premier élément (non obligatoire)

22. }

24. onSelectedChoice(item: IItemBtm) {           // lien avec le composant réutilisable enfant

25. this.selectedChoice = item;                // (1) on réceptionne le choix de l'utilisateur pour l'afficher dans la vue

26. //

27. //  traitement

28. //

29. }

30. }

\pages\page2\page2.component.html

1. <p>page2 works!</p>

3. html:a53a986be9b1f52de0cf56f27634e343dae5b05d

5. <app-button-toggle-mat

6. [items]="colors"

7. [group]="'color'"

8. (selectedItemEvent)="onSelectedColor($event)"

9. ></app-button-toggle-mat>

11. (1) Couleur={{selectedColor|json}}

13. html:3087918116cd08eac5d04ee6ca68db7e1be92024

16. html:ef648c4b35586de068ddd565a237c04bc83f9409

18. <app-button-toggle-mat

19. [items]="choices"

20. [selectedItem]="selectedChoice"

21. [group]="'choice'"

22. (selectedItemEvent)="onSelectedChoice($event)"

23. ></app-button-toggle-mat>

25. (1) choice={{selectedChoice|json}}

\pages\page2\page2.component.ts

1. import { Component, OnInit } from '@angular/core';

2. import { IItemBtm } from '../../shared/button-toggle-mat/models/i-item-btm';

3. import { ItemsColor } from '../../data/param';

4. import { ItemsChoice } from '../../data/param';

6. @Component({

7. selector: 'app-page2',

8. templateUrl: './page2.component.html',

9. styleUrls: ['./page2.component.scss']

10. })

11. export class Page2Component implements OnInit {

13. //  Choix d'une couleur

14. colors: IItemBtm[];                         // on éxige que les éléments du tableau doivent être du type : IItemBtm

15. selectedColor: IItemBtm;                    // pour contenir le choix de l'utilisateur

17. //  Choix d'un item quelconque

18. choices: IItemBtm[];                        // on éxige que les éléments du tableau doivent être du type : IItemBtm

19. selectedChoice: IItemBtm;                   // pour contenir le choix de l'utilisateur

21. constructor() { }

23. ngOnInit(): void {                          // initialisation des données

24. //  Choix d'une couleur

25. this.colors = ItemsColor;                 // on fourni une liste d'éléments au composant réutilisable

26. // sans valeur par défaut

27. //  Choix d'un item quelconque

28. this.choices = ItemsChoice;               // on fourni une liste d'éléments au composant réutilisable

29. this.selectedChoice = this.choices[0];    // avec une valeur par défaut

30. }

32. //  Choix d'une couleur

33. onSelectedColor(item: IItemBtm) {           // lien avec le composant réutilisable enfant

34. this.selectedColor = item;                // (1) on réceptionne le choix de l'utilisateur pour l'afficher dans la vue

35. //

36. //  traitement

37. //

38. }

40. //  Choix d'un item quelconque

41. onSelectedChoice(item: IItemBtm) {           // lien avec le composant réutilisable enfant

42. this.selectedChoice = item;                // (1) on réceptionne le choix de l'utilisateur pour l'afficher dans la vue

43. //

44. //  traitement

45. //

46. }

47. }

\shared\button-toggle-mat\button-toggle-mat.module.ts

1. import { NgModule } from '@angular/core';

2. import { CommonModule } from '@angular/common';

3. //

4. import { ButtonToggleMatComponent } from './components/button-toggle-mat/button-toggle-mat.component';

5. import { MatButtonToggleModule } from '@angular/material/button-toggle';

6. import { StoredService } from './services/stored.service';

9. @NgModule({

10. declarations: [ButtonToggleMatComponent],

11. imports: [

12. CommonModule,

13. MatButtonToggleModule,              // on importe uniquement le module : MatButton d'Angular Material pour pouvoir utiliser son composant

15. ],

16. exports: [

17. ButtonToggleMatComponent,           // on exporte le composant pour qu'il soit utilisable lors d'un import

18. ],

19. providers: [StoredService, ]          // les composants de ce module auront accès à cette instance du service

20. })                                      // donc : ButtonToggleMatComponent des pages : page1 et page2 aura accès à cette instance

21. export class ButtonToggleMatModule { }

\shared\button-toggle-mat\models\i-item-btm.ts

1. export interface IItemBtm {

2. key: string;

3. value: string;

4. }

\shared\button-toggle-mat\services\stored.service.ts

1. import { IItemBtm } from '../models/i-item-btm';

3. export class StoredService {

5. selectedItemGroup: IItemBtm[] = [];       //  un tableau contenant les couples :  'nom de groupe': sélection utilisateur

7. constructor() { }

9. getSelectedItem(group: string): IItemBtm {          // on récupère le choix utilisateur par son groupe

10. return (undefined !== this.selectedItemGroup[group]) ? this.selectedItemGroup[group] : undefined;

11. }

13. setSelectedItem(item: IItemBtm, group: string) {    // on enregistre le choix utilisateur par son groupe

14. this.selectedItemGroup[group] = item;

15. }

17. getInitializedItem(defaultItem: IItemBtm, group: string): IItemBtm {      // on calcul en fonction de la valeur par défaut et de la valeur enregistré du tableau

18. if (defaultItem && this.getSelectedItem(group) === undefined) {         // si il y a une valeur par défaut et pas de sélection utilisateur enregistré alors...

19. this.setSelectedItem(defaultItem, group);                             // c'est le choix par défaut qui est pris en compte

20. return defaultItem;

21. }

23. return this.getSelectedItem(group);                                     // sinon c'est le choix enregistré dans le tableau qui est pris en compte

24. }

25. }

\shared\button-toggle-mat\components\button-toggle-mat\button-toggle-mat.component.html

1. <p>

2. Votre choix ? &nbsp;

3. <mat-button-toggle-group name="fontStyle" aria-label="Font Style" #group="matButtonToggleGroup" [value]="selectedItem?.key" (change)="onChange($event)">

4. <ng-container *ngFor="let item of items">

5. <mat-button-toggle value="{{item.key}}" >{{item.value}}</mat-button-toggle>

6. </ng-container>

7. </mat-button-toggle-group>

8. </p>

\shared\button-toggle-mat\components\button-toggle-mat\button-toggle-mat.component.ts

1. import { Component, Input, OnInit, Output, EventEmitter } from '@angular/core';

2. import { IItemBtm } from '../../models/i-item-btm';

3. import { StoredService } from '../../services/stored.service';

5. @Component({

6. selector: 'app-button-toggle-mat',

7. templateUrl: './button-toggle-mat.component.html',

8. styleUrls: ['./button-toggle-mat.component.scss'],

9. })

10. export class ButtonToggleMatComponent implements OnInit {

11. @Input() items: IItemBtm[];                                   // réceptionne la liste des éléments

12. @Input() selectedItem: IItemBtm;                              // l'élément qui doit être sélectionné par défaut

13. @Input() group: string;                                       // le groupe auquel appartient les éléments

14. @Output() selectedItemEvent = new EventEmitter<IItemBtm>();   // envoi le choix de l'utilisateur au parent : page1 ou page2

16. constructor(private storedService: StoredService) { }

18. ngOnInit(): void {                                            // à l'initialisation du composant

19. this.selectedItem = this.storedService.getInitializedItem(this.selectedItem, this.group);   // on calcul quel item est sélectionné au 1er affichage

20. this.selectedItemEvent.emit(this.selectedItem);                                             // on retourne cet item au parent : page1 ou page2

21. }

23. onChange(event: any) {                                        // quand un choix utilisateur est fait

24. // event.value ne contient que : key

25. // on veut pouvoir retourner l'objet entier (key + value)

26. // donc on va le chercher dans la liste des items à partir de sa clé : key

27. const item: IItemBtm = this.items.filter((item: IItemBtm) => item.key == event.value)[0];   // on parcourt la liste des items et si on trouve la correspondance avec: key

28. // alors on retourne l'objet trouvé

29. this.storedService.setSelectedItem(item, this.group);           // on enregistre le choix

30. this.selectedItemEvent.emit(item);                              // on retourne le choix de l'utilisateur au parent : page1 ou page2

31. }

32. }

I-D - Résultat

- quand on sélectionne un choix celui çi est envoyé à son parent : page1.component ou page2.component ;

- remarquez que de page1 à page2 et vice versa, le choix 'quelconque' garde la sélection que l'on a choisie grace au même nom de groupe.

I-E - Conclusion

- le composant Web peut être utilisé plusieurs fois, il suffit de copier coller le dossier : /button-toggle-mat dans un autre projet et l'utiliser tel quel ;

- comme le composant ne dépend pas d'une liste défini, on peut lui transmettre n'importe quelle liste à condition qu'elle respecte le modèle ;

- il suffit de lui transmettre une liste à afficher, un nom de groupe et si besoin un élément par défaut ;

- à savoir que lors du routing, l'accès à une page engendre l'initialisation de son composant page et de ses données.

II - Mise en production : Firebase hosting

Utilisons le service Hosting de firebase pour mettre en production une application Angular.
Ce service est gratuit et limité mais cela suffit largement pour tester.

(1)
On va utiliser le projet : angular-re-use1 pour le mettre en production

copier / coller le projet : angular-re-use1 et renommez le dossier en : angular-hosting1

sur le nouveau dossier : angular-hosting1 faite une recherche global et remplacer tous les mots : 'angular-re-use1' en 'angular-hosting1'
(sur visual studio code -> clique droit sur le dossier -> find in folder -> angular-re-use1 en : angular-hosting1)

(2) ou utiliser n'importe quel projet qui tourne en local

II-A - Pratique

Créer un compte et se connecter :

https://firebase.google.com/

- une fois connecté, il faut créer un projet firebase ;

- ce projet proposera divers services : base de données firebase, google analytics, hosting, functions....

- nous allons juste utiliser le servide Hosting pour deployer notre application.

1. -> Accéder à la console -> Ajouter un projet -> nom du projet : 'hosting1' -> continuer

3. Configurer Google Analytics -> Créer un compte : 'hosting1-google-analytics' -> enregistrer

5. -> créer un projet

7. menu de gauche -> hosting -> commencer

On installe en global les outils pour angular-cli afin de pouvoir lancer les commandes firebase :

1. npm install -g firebase-tools@latest

il faut se connecter afin que angular-cli soit lié avec le compte firebase que vous avez créé :

1. firebase login                          // le navigateur chrome va s'ouvrir pour vous demandez de vous connecter

La commande suivante va éffectuer quelques modifications des fichiers de votre projet pour initialiser le déploiement :

1. firebase init

2. ? Are you ready to proceed? (Y/n)   Y

3. -> Use an existing project          Y

5. (*) Hosting             <barre espace>      pour sélectionner

6. <touche entrée>     pour valider

8. -> Select a default Firebase project for this directory:                      hosting1-......

10. -> ? What do you want to use as your public directory?                        dist/angular-hosting1

12. -> ? Configure as a single-page app (rewrite all urls to /index.html)? (y/N)  Y

14. -> ? Set up automatic builds and deploys with GitHub? (y/N)                   N

La première fois et à chaque fois que vous mettez en production, il faut lancer les 2 commandes suivantes :

1. ng build --prod                             // toujours builder en : --prod avant le deploiement

2. firebase deploy

II-B - à savoir

C'est le contenu du dossier : /dist/angular-hosting1 qui est déployé dans le cloud Hosting

II-C - Résultat

Et voilà, plus qu'à accéder à l'application en ligne
https://hosting1-.......web.app/#/ (voir le lien affiché à la fin du : firebase deploy)