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▲
2.
3.
4.
5.
6.
7.
8.
9.
10.
- /shared/button-toggle-mat
- /components/button-toggle-mat.component.ts // le composant
- /services/stored.service.ts // pour enregistrer le choix de l'utilisateur
- /models/i-item-btm.ts // le type de donnée que le composant manipule
- button-toggle-mat.module.ts // déclare le composant et le service. De plus, exporte le composant
- /pages
- /page1 // composant avec une liste d'item 'quelconque' ayant comme nom de groupe : 'choice'
- /page2 // composant avec une liste d'item 'couleur' ayant comme nom de groupe : 'color'
// composant avec une liste d'item 'quelconque' ayant comme nom de groupe : 'choice'
I-C. Pratique▲
Créer un nouveau projet : angular-re-use1
2.
3.
4.
5.
6.
7.
8.
9.
10.
11.
12.
13.
14.
15.
16.
ng new angular-re-use1
routing ? YES
SCSS
ng add @angular/material
ng g m pages --module=app
ng g m pages/page1 --module=pages
ng g m pages/page2 --module=pages
ng g c pages/page1 --module=page1
ng g c pages/page2 --module=page2
ng g m shared/button-toggle-mat --module=app
ng g c shared/button-toggle-mat/components/button-toggle-mat --module=button-toggle-mat
ng g i shared/button-toggle-mat/models/i-item-btm
ng g s shared/button-toggle-mat/services/stored
app.module.ts
2.
3.
4.
5.
6.
7.
8.
9.
10.
11.
12.
13.
14.
15.
16.
17.
18.
19.
20.
21.
22.
23.
import
{
BrowserModule }
from
'@angular/platform-browser'
;
import
{
NgModule }
from
'@angular/core'
;
import
{
AppRoutingModule }
from
'./app-routing.module'
;
import
{
AppComponent }
from
'./app.component'
;
import
{
NoopAnimationsModule }
from
'@angular/platform-browser/animations'
;
//
import
{
PagesModule }
from
'./pages/pages.module'
;
@NgModule
({
declarations
:
[
AppComponent,
],
imports
:
[
BrowserModule,
AppRoutingModule,
NoopAnimationsModule,
//
PagesModule,
// pour le routing
],
providers
:
[],
bootstrap
:
[
AppComponent]
}
)
export
class
AppModule {
}
app-routing.module.ts
2.
3.
4.
5.
6.
7.
8.
9.
10.
11.
12.
13.
14.
15.
16.
import
{
NgModule }
from
'@angular/core'
;
import
{
Routes,
RouterModule }
from
'@angular/router'
;
//
import
{
Page1Component }
from
'src/app/pages/page1/page1.component'
;
import
{
Page2Component }
from
'src/app/pages/page2/page2.component'
;
const
routes
:
Routes =
[
{
path
:
'page1'
,
component
:
Page1Component },
{
path
:
'page2'
,
component
:
Page2Component },
];
@NgModule
({
imports
:
[
RouterModule.forRoot
(
routes)],
exports
:
[
RouterModule]
}
)
export
class
AppRoutingModule {
}
app.component.html
2.
3.
4.
5.
6.
<ul>
<li><a [routerLink]=
"['/page1']"
>
aller à page1</a></li>
<li><a [routerLink]=
"['/page2']"
>
aller à page2</a></li>
</ul>
<router-outlet></router-outlet>
\data\param.ts
2.
3.
4.
5.
6.
7.
8.
9.
10.
11.
12.
export
const
ItemsChoice =
[
{
key
:
'K0'
,
value
:
'Aucun'
},
{
key
:
'K1'
,
value
:
'Choix 1'
},
{
key
:
'K2'
,
value
:
'Choix 2'
},
{
key
:
'K3'
,
value
:
'Choix 3'
},
];
export
const
ItemsColor =
[
{
key
:
'NONE'
,
value
:
'Aucune'
},
{
key
:
'V'
,
value
:
'Vert'
},
{
key
:
'R'
,
value
:
'Rouge'
},
];
\pages\page1\page1.module.ts
2.
3.
4.
5.
6.
7.
8.
9.
10.
11.
12.
13.
14.
15.
16.
import
{
NgModule }
from
'@angular/core'
;
import
{
CommonModule }
from
'@angular/common'
;
//
import
{
ButtonToggleMatModule }
from
'../../shared/button-toggle-mat/button-toggle-mat.module'
;
import
{
Page1Component }
from
'./page1.component'
;
@NgModule
({
declarations
:
[
Page1Component],
imports
:
[
CommonModule,
//
ButtonToggleMatModule,
// utilise le composant réutilisable du module
]
}
)
export
class
Page1Module {
}
\pages\page2\page2.module.ts
2.
3.
4.
5.
6.
7.
8.
9.
10.
11.
12.
13.
14.
15.
16.
17.
import
{
NgModule }
from
'@angular/core'
;
import
{
CommonModule }
from
'@angular/common'
;
//
import
{
ButtonToggleMatModule }
from
'../../shared/button-toggle-mat/button-toggle-mat.module'
;
import
{
Page2Component }
from
'./page2.component'
;
@NgModule
({
declarations
:
[
Page2Component],
imports
:
[
CommonModule,
//
ButtonToggleMatModule,
// utilise le composant réutilisable du module
]
}
)
export
class
Page2Module {
}
\pages\pages.module.ts
2.
3.
4.
5.
6.
7.
8.
9.
10.
11.
12.
13.
14.
15.
16.
17.
import
{
NgModule }
from
'@angular/core'
;
import
{
CommonModule }
from
'@angular/common'
;
//
import
{
Page1Module }
from
'./page1/page1.module'
;
import
{
Page2Module }
from
'./page2/page2.module'
;
@NgModule
({
declarations
:
[],
imports
:
[
CommonModule,
//
Page1Module,
Page2Module,
]
}
)
export
class
PagesModule {
}
\pages\page1\page1.component.html
2.
3.
4.
5.
6.
7.
8.
9.
10.
11.
12.
<p>page1 works!</p>
html:ef648c4b35586de068ddd565a237c04bc83f9409
<app-button-toggle-mat
[items]=
"choices"
[selectedItem]=
"selectedChoice"
[group]=
"'choice'"
(selectedItemEvent)=
"onSelectedChoice($event)"
></app-button-toggle-mat>
(1) choice={{selectedChoice|json}}
\pages\page1\page1.component.ts
2.
3.
4.
5.
6.
7.
8.
9.
10.
11.
12.
13.
14.
15.
16.
17.
18.
19.
20.
21.
22.
23.
24.
25.
26.
27.
28.
29.
30.
import
{
Component,
OnInit }
from
'@angular/core'
;
import
{
IItemBtm }
from
'../../shared/button-toggle-mat/models/i-item-btm'
;
import
{
ItemsChoice }
from
'../../data/param'
;
@Component
({
selector
:
'app-page1'
,
templateUrl
:
'./page1.component.html'
,
styleUrls
:
[
'./page1.component.scss'
]
}
)
export
class
Page1Component implements
OnInit {
choices
:
IItemBtm[];
// on éxige que ces éléments du tableau doivent être du type : IItemBtm
// ainsi, il ne peut y avoir d'erreur car notre composant ne gère que ce type de donnée : IItemBtm
selectedChoice
:
IItemBtm;
// on fourni l'élément par défaut au composant réutilisable (non obligatoire) (2)
// et en même temps va contenir le choix de l'utilisateur
constructor
(
) {
}
ngOnInit
(
):
void
{
this
.
choices =
ItemsChoice;
// on fourni une liste d'éléments au composant réutilisable
this
.
selectedChoice =
this
.
choices[
0
];
// (2) par défaut, l'élément qui sera sélectionné sera le premier élément (non obligatoire)
}
onSelectedChoice
(
item
:
IItemBtm) {
// lien avec le composant réutilisable enfant
this
.
selectedChoice =
item;
// (1) on réceptionne le choix de l'utilisateur pour l'afficher dans la vue
//
// traitement
//
}
}
\pages\page2\page2.component.html
2.
3.
4.
5.
6.
7.
8.
9.
10.
11.
12.
13.
14.
15.
16.
17.
18.
19.
20.
21.
22.
23.
24.
25.
<p>page2 works!</p>
html:a53a986be9b1f52de0cf56f27634e343dae5b05d
<app-button-toggle-mat
[items]=
"colors"
[group]=
"'color'"
(selectedItemEvent)=
"onSelectedColor($event)"
></app-button-toggle-mat>
(1) Couleur={{selectedColor|json}}
html:3087918116cd08eac5d04ee6ca68db7e1be92024
html:ef648c4b35586de068ddd565a237c04bc83f9409
<app-button-toggle-mat
[items]=
"choices"
[selectedItem]=
"selectedChoice"
[group]=
"'choice'"
(selectedItemEvent)=
"onSelectedChoice($event)"
></app-button-toggle-mat>
(1) choice={{selectedChoice|json}}
\pages\page2\page2.component.ts
2.
3.
4.
5.
6.
7.
8.
9.
10.
11.
12.
13.
14.
15.
16.
17.
18.
19.
20.
21.
22.
23.
24.
25.
26.
27.
28.
29.
30.
31.
32.
33.
34.
35.
36.
37.
38.
39.
40.
41.
42.
43.
44.
45.
46.
47.
import
{
Component,
OnInit }
from
'@angular/core'
;
import
{
IItemBtm }
from
'../../shared/button-toggle-mat/models/i-item-btm'
;
import
{
ItemsColor }
from
'../../data/param'
;
import
{
ItemsChoice }
from
'../../data/param'
;
@Component
({
selector
:
'app-page2'
,
templateUrl
:
'./page2.component.html'
,
styleUrls
:
[
'./page2.component.scss'
]
}
)
export
class
Page2Component implements
OnInit {
// Choix d'une couleur
colors
:
IItemBtm[];
// on éxige que les éléments du tableau doivent être du type : IItemBtm
selectedColor
:
IItemBtm;
// pour contenir le choix de l'utilisateur
// Choix d'un item quelconque
choices
:
IItemBtm[];
// on éxige que les éléments du tableau doivent être du type : IItemBtm
selectedChoice
:
IItemBtm;
// pour contenir le choix de l'utilisateur
constructor
(
) {
}
ngOnInit
(
):
void
{
// initialisation des données
// Choix d'une couleur
this
.
colors =
ItemsColor;
// on fourni une liste d'éléments au composant réutilisable
// sans valeur par défaut
// Choix d'un item quelconque
this
.
choices =
ItemsChoice;
// on fourni une liste d'éléments au composant réutilisable
this
.
selectedChoice =
this
.
choices[
0
];
// avec une valeur par défaut
}
// Choix d'une couleur
onSelectedColor
(
item
:
IItemBtm) {
// lien avec le composant réutilisable enfant
this
.
selectedColor =
item;
// (1) on réceptionne le choix de l'utilisateur pour l'afficher dans la vue
//
// traitement
//
}
// Choix d'un item quelconque
onSelectedChoice
(
item
:
IItemBtm) {
// lien avec le composant réutilisable enfant
this
.
selectedChoice =
item;
// (1) on réceptionne le choix de l'utilisateur pour l'afficher dans la vue
//
// traitement
//
}
}
\shared\button-toggle-mat\button-toggle-mat.module.ts
2.
3.
4.
5.
6.
7.
8.
9.
10.
11.
12.
13.
14.
15.
16.
17.
18.
19.
20.
21.
import
{
NgModule }
from
'@angular/core'
;
import
{
CommonModule }
from
'@angular/common'
;
//
import
{
ButtonToggleMatComponent }
from
'./components/button-toggle-mat/button-toggle-mat.component'
;
import
{
MatButtonToggleModule }
from
'@angular/material/button-toggle'
;
import
{
StoredService }
from
'./services/stored.service'
;
@NgModule
({
declarations
:
[
ButtonToggleMatComponent],
imports
:
[
CommonModule,
MatButtonToggleModule,
// on importe uniquement le module : MatButton d'Angular Material pour pouvoir utiliser son composant
],
exports
:
[
ButtonToggleMatComponent,
// on exporte le composant pour qu'il soit utilisable lors d'un import
],
providers
:
[
StoredService,
]
// les composants de ce module auront accès à cette instance du service
}
) // donc : ButtonToggleMatComponent des pages : page1 et page2 aura accès à cette instance
export
class
ButtonToggleMatModule {
}
\shared\button-toggle-mat\models\i-item-btm.ts
2.
3.
4.
export
interface
IItemBtm {
key
:
string
;
value
:
string
;
}
\shared\button-toggle-mat\services\stored.service.ts
2.
3.
4.
5.
6.
7.
8.
9.
10.
11.
12.
13.
14.
15.
16.
17.
18.
19.
20.
21.
22.
23.
24.
25.
import
{
IItemBtm }
from
'../models/i-item-btm'
;
export
class
StoredService {
selectedItemGroup
:
IItemBtm[]
=
[];
// un tableau contenant les couples : 'nom de groupe': sélection utilisateur
constructor
(
) {
}
getSelectedItem
(
group
:
string
):
IItemBtm {
// on récupère le choix utilisateur par son groupe
return
(
undefined
!==
this
.
selectedItemGroup[
group]
) ?
this
.
selectedItemGroup[
group]
:
undefined
;
}
setSelectedItem
(
item
:
IItemBtm,
group
:
string
) {
// on enregistre le choix utilisateur par son groupe
this
.
selectedItemGroup[
group]
=
item;
}
getInitializedItem
(
defaultItem
:
IItemBtm,
group
:
string
):
IItemBtm {
// on calcul en fonction de la valeur par défaut et de la valeur enregistré du tableau
if
(
defaultItem &&
this
.getSelectedItem
(
group) ===
undefined
) {
// si il y a une valeur par défaut et pas de sélection utilisateur enregistré alors...
this
.setSelectedItem
(
defaultItem,
group);
// c'est le choix par défaut qui est pris en compte
return
defaultItem;
}
return
this
.getSelectedItem
(
group);
// sinon c'est le choix enregistré dans le tableau qui est pris en compte
}
}
\shared\button-toggle-mat\components\button-toggle-mat\button-toggle-mat.component.html
2.
3.
4.
5.
6.
7.
8.
<p>
Votre choix ?
<mat-button-toggle-group name
=
"fontStyle"
aria-label
=
"Font Style"
#group
=
"matButtonToggleGroup"
[value]=
"selectedItem?.key"
(change)=
"onChange($event)"
>
<ng-container *ngFor
=
"let item of items"
>
<mat-button-toggle value
=
"{{item.key}}"
>{{item.value}}</mat-button-toggle>
</ng-container>
</mat-button-toggle-group>
</p>
\shared\button-toggle-mat\components\button-toggle-mat\button-toggle-mat.component.ts
2.
3.
4.
5.
6.
7.
8.
9.
10.
11.
12.
13.
14.
15.
16.
17.
18.
19.
20.
21.
22.
23.
24.
25.
26.
27.
28.
29.
30.
31.
32.
import
{
Component,
Input,
OnInit,
Output,
EventEmitter }
from
'@angular/core'
;
import
{
IItemBtm }
from
'../../models/i-item-btm'
;
import
{
StoredService }
from
'../../services/stored.service'
;
@Component
({
selector
:
'app-button-toggle-mat'
,
templateUrl
:
'./button-toggle-mat.component.html'
,
styleUrls
:
[
'./button-toggle-mat.component.scss'
],
}
)
export
class
ButtonToggleMatComponent implements
OnInit {
@Input
(
) items
:
IItemBtm[];
// réceptionne la liste des éléments
@Input
(
) selectedItem
:
IItemBtm;
// l'élément qui doit être sélectionné par défaut
@Input
(
) group
:
string
;
// le groupe auquel appartient les éléments
@Output
(
) selectedItemEvent =
new
EventEmitter<
IItemBtm>(
);
// envoi le choix de l'utilisateur au parent : page1 ou page2
constructor
(
private
storedService
:
StoredService) {
}
ngOnInit
(
):
void
{
// Ã l'initialisation du composant
this
.
selectedItem =
this
.
storedService.getInitializedItem
(
this
.
selectedItem,
this
.
group);
// on calcul quel item est sélectionné au 1er affichage
this
.
selectedItemEvent.emit
(
this
.
selectedItem);
// on retourne cet item au parent : page1 ou page2
}
onChange
(
event
:
any
) {
// quand un choix utilisateur est fait
// event.value ne contient que : key
// on veut pouvoir retourner l'objet entier (key + value)
// donc on va le chercher dans la liste des items à partir de sa clé : key
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
// alors on retourne l'objet trouvé
this
.
storedService.setSelectedItem
(
item,
this
.
group);
// on enregistre le choix
this
.
selectedItemEvent.emit
(
item);
// on retourne le choix de l'utilisateur au parent : page1 ou page2
}
}
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.
- pour pouvoir enregistrer des données afin de les récupérer lors de l'initialisation d'un composant page on se sert d'un service pour stocker les données (car son instance est un singleton).
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 :
- 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.
2.
3.
4.
5.
6.
7.
-> Accéder à la console -> Ajouter un projet -> nom du projet : 'hosting1' -> continuer
Configurer Google Analytics -> Créer un compte : 'hosting1-google-analytics' -> enregistrer
-> créer un projet
menu de gauche -> hosting -> commencer
On installe en global les outils pour angular-cli afin de pouvoir lancer les commandes firebase :
npm install -g firebase-tools@latest
il faut se connecter afin que angular-cli soit lié avec le compte firebase que vous avez créé :
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 :
2.
3.
4.
5.
6.
7.
8.
9.
10.
11.
12.
13.
14.
firebase init
? Are you ready to proceed? (Y/n) Y
-> Use an existing project Y
(*) Hosting <barre espace> pour sélectionner
<touche entrée> pour valider
-> Select a default Firebase project for this directory: hosting1-......
-> ? What do you want to use as your public directory? dist/angular-hosting1
-> ? Configure as a single-page app (rewrite all urls to /index.html)? (y/N) Y
-> ? 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 :
2.
ng build --prod // toujours builder en : --prod avant le deploiement
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)