L'intérêt d'utiliser Angular est de pouvoir gérer le DOM (Document Object Model : les éléments HTML affichés par le navigateur) de manière dynamique, et pour cela, il faut utiliser la liaison de données, ou "databinding".
Le databinding, c'est la communication entre votre code TypeScript et le template HTML qui est montré à l'utilisateur. Cette communication est divisée en deux directions :
les informations venant de votre code qui doivent être affichées dans le navigateur, comme par exemple des informations que votre code a calculé ou récupéré sur un serveur. Les deux principales méthodes pour cela sont le "string interpolation" et le "property binding" ;
les informations venant du template qui doivent être gérées par le code : l'utilisateur a rempli un formulaire ou cliqué sur un bouton, et il faut réagir et gérer ces événements. On parlera de "event binding" pour cela.
Il existe également des situations comme les formulaires, par exemple, où l'on voudra une communication à double sens : on parle donc de "two-way binding".
String interpolation
L'interpolation est la manière la plus basique d'émettre des données issues de votre code TypeScript.
Imaginez une application qui vérifie l'état de vos appareils électriques à la maison pour voir s'ils sont allumés ou non. Créez maintenant un nouveau component AppareilComponent
avec la commande suivante :
ng generate component appareil
Ouvrez ensuite appareil.component.html
(dans le nouveau dossier appareil
créé par le CLI), supprimez le contenu, et entrez le code ci-dessous :
class="list-group-item"
Ceci est dans AppareilComponent
Ensuite, ouvrez app.component.html
, et remplacez tout le contenu comme suit :
class="container"
class="row"
class="col-xs-12"
Mes appareils
class="list-group"
Maintenant votre navigateur devrait vous montrer quelque chose comme cela :
Pour l'instant, rien de bien spectaculaire. Vous allez maintenant utiliser l'interpolation pour commencer à dynamiser vos données. Modifiez appareil.component.html
ainsi :
class="list-group-item"
Appareil : {{ appareilName }}
Ici, vous trouvez la syntaxe pour l'interpolation : les doubles accolades {{ }}
. Ce qui se trouve entre les doubles accolades correspond à l'expression TypeScript que nous voulons afficher, l'expression la plus simple étant une variable. D'ailleurs, puisque la variable appareilName
n'existe pas encore, votre navigateur n'affiche rien à cet endroit pour l'instant. Ouvrez maintenant appareil.component.ts
:
import { Component, OnInit } from '@angular/core';
@Component({
selector: 'app-appareil',
templateUrl: './appareil.component.html',
styleUrls: ['./appareil.component.scss']
})
export class AppareilComponent implements OnInit {
constructor() { }
ngOnInit() {
}
}
Ajoutez maintenant la ligne de code suivante en haut de la déclaration de class :
export class AppareilComponent implements OnInit {
appareilName: string = 'Machine à laver';
constructor() { }
Une fois le fichier enregistré, votre navigateur affiche maintenant :
Voilà ! Vous avez maintenant une communication entre votre code TypeScript et votre template HTML. Pour l'instant, les valeurs sont codées "en dur", mais à terme, ces valeurs peuvent être calculées par votre code ou récupérées sur un serveur, par exemple. Ajoutez maintenant une nouvelle variable dans votre AppareilComponent
:
appareilName: string = 'Machine à laver';
appareilStatus: string = 'éteint';
Puis intégrez cette variable dans le template :
class="list-group-item"
Appareil : {{ appareilName }} -- Statut : {{ appareilStatus }}
Votre navigateur montre ceci :
On peut utiliser toute expression TypeScript valable pour l'interpolation. Pour démontrer cela, ajouter une méthode au fichier AppareilComponent
:
import { Component, OnInit } from '@angular/core';
@Component({
selector: 'app-appareil',
templateUrl: './appareil.component.html',
styleUrls: ['./appareil.component.scss']
})
export class AppareilComponent implements OnInit {
appareilName: string = 'Machine à laver';
appareilStatus: string = 'éteint';
constructor() { }
ngOnInit() {
}
getStatus() {
return this.appareilStatus;
}
}
Alors que cette méthode ne fait que retourner la même valeur qu'avant, on peut imaginer une situation où elle ferait un appel API, par exemple, qui retournerait le statut de l'appareil électrique.
Modifiez maintenant le template pour prendre en compte ce changement :
class="list-group-item"
Appareil : {{ appareilName }} -- Statut : {{ getStatus() }}
Vous devez avoir le même résultat visuel dans le navigateur.
Property binding
La liaison par propriété ou "property binding" est une autre façon de créer de la communication dynamique entre votre TypeScript et votre template : plutôt qu'afficher simplement le contenu d'une variable, vous pouvez modifier dynamiquement les propriétés d'un élément du DOM en fonction de données dans votre TypeScript.
Pour votre application des appareils électriques, imaginez que si l'utilisateur est authentifié, on lui laisse la possibilité d'allumer tous les appareils de la maison. Puisque l'authentification est une valeur globale, ajoutez une variable boolean dans AppComponent
, votre component de base (vous pouvez supprimer la variable title
puisque vous ne l'utilisez plus) :
import { Component } from '@angular/core';
@Component({
selector: 'app-root',
templateUrl: './app.component.html',
styleUrls: ['./app.component.scss']
})
export class AppComponent {
isAuth = false;
}
Ajoutez maintenant un bouton au template global app.component.html
, en dessous de la liste d'appareils :
class="container"
class="row"
class="col-xs-12"
Mes appareils
class="list-group"
class="btn btn-success" disabledTout allumer
La propriété disabled
permet de désactiver le bouton. Afin de lier cette propriété au TypeScript, il faut le mettre entre crochets []
et l'associer à la variable ainsi :
class="btn btn-success" [disabled]="!isAuth"Tout allume
Le point d'exclamation fait que le bouton est désactivé lorsque isAuth === false
. Pour montrer que cette liaison est dynamique, créez une méthode constructor
dans AppComponent
, dans laquelle vous créerez une timeout qui associe la valeur true
à isAuth
après 4 secondes (pour simuler, par exemple, le temps d'un appel API) :
export class AppComponent {
isAuth = false;
constructor() {
setTimeout(
() => {
this.isAuth = true;
}, 4000
);
}
}
Pour en voir l'effet, rechargez la page dans votre navigateur et observez comment le bouton s'active au bout de quatre secondes. Pour l'instant le bouton ne fait rien : vous découvrirez comment exécuter du code lorsque l'utilisateur cliquera dessus avec la liaison des événements, ou "event binding".
Event binding
Avec le string interpolation et le property binding, vous savez communiquer depuis votre code TypeScript vers le template HTML. Maintenant, je vais vous montrer comment réagir dans votre code TypeScript aux événements venant du template HTML.
Actuellement, vous avez un bouton sur votre template qui s'active au bout de 4 secondes. Vous allez maintenant lui ajouter une fonctionnalité liée à l'événement "click" (déclenché quand l'utilisateur clique dessus).
Ajoutez la liaison suivante à votre bouton dans le template HTML :
class="container"
class="row"
class="col-xs-12"
Mes appareils
class="list-group"
class="btn btn-success"
[disabled]="!isAuth"
(click)="onAllumer()"Tout allumer
Comme vous pouvez le constater, on utilise les parenthèses ()
pour créer une liaison à un événement. Pour l'instant, la méthode onAllumer()
n'existe pas, donc je vous propose de la créer maintenant dans app.component.ts
, en dessous du constructeur.
La méthode affichera simplement un message dans la console dans un premier temps :
onAllumer() {
console.log('On allume tout !');
}
Enregistrez le fichier, et ouvrez la console dans votre navigateur. Lorsque le bouton s'active, cliquez dessus, et vous verrez votre message apparaître dans la console.
Même si cela reste une fonction très simple pour l'instant, cela vous montre comment lier une fonction TypeScript à un événement venant du template. De manière générale, vous pouvez lier du code à n'importe quelle propriété ou événement des éléments du DOM. Pour plus d'informations, vous pouvez consulter le Mozilla Developer Network ou W3Schools, par exemple.
Two-way binding
La liaison à double sens (ou two-way binding) utilise la liaison par propriété et la liaison par événement en même temps ; on l'utilise, par exemple, pour les formulaires, afin de pouvoir déclarer et de récupérer le contenu des champs, entre autres.
Pour pouvoir utiliser le two-way binding, il vous faut importer FormsModule
depuis @angular/forms dans votre application. Vous pouvez accomplir cela en l'ajoutant à l'array imports
de votre AppModule
(sans oublier d'ajouter le statement import
correspondant en haut du fichier) :
import { BrowserModule } from '@angular/platform-browser';
import { NgModule } from '@angular/core';
import { AppComponent } from './app.component';
import { MonPremierComponent } from './mon-premier/mon-premier.component';
import { AppareilComponent } from './appareil/appareil.component';
import { FormsModule } from '@angular/forms';
@NgModule({
declarations: [
AppComponent,
MonPremierComponent,
AppareilComponent
],
imports: [
BrowserModule,
FormsModule
],
providers: [],
bootstrap: [AppComponent]
})
export class AppModule { }
Le two-way binding emploie le mélange des syntaxes de property binding et d'event binding : des crochets et des parenthèses [()]
. Pour une première démonstration, ajoutez un <input>
dans votre template appareil.component.html
et liez-le à la variable appareilName
en utilisant la directive ngModel
:
class="list-group-item"
Appareil : {{ appareilName }} -- Statut : {{ getStatus() }}
type="text" class="form-control" [(ngModel)]="appareilName"
Dans votre template, vous verrez un <input>
par appareil. Le nom de l'appareil est déjà indiqué dedans, et si vous le modifiez, le contenu du <h4>
est modifié avec. Ainsi vous voyez également que chaque instance du component AppareilComponent
est entièrement indépendante une fois créée : le fait d'en modifier une ne change rien aux autres. Ce concept est très important, et il s'agit de l'une des plus grandes utilités d'Angular.
Propriétés personnalisées
Il est possible de créer des propriétés personnalisées dans un component afin de pouvoir lui transmettre des données depuis l'extérieur.
Pour l'application des appareils électriques, il serait intéressant de faire en sorte que chaque instance d' AppareilComponent
ait un nom différent qu'on puisse régler depuis l'extérieur du code. Pour ce faire, il faut utiliser le décorateur @Input()
en remplaçant la déclaration de la variable appareilName
:
import { Component, Input, OnInit } from '@angular/core';
@Component({
selector: 'app-appareil',
templateUrl: './appareil.component.html',
styleUrls: ['./appareil.component.scss']
})
export class AppareilComponent implements OnInit {
@Input() appareilName: string;
appareilStatus: string = 'éteint';
constructor() { }
ngOnInit() {
}
getStatus() {
return this.appareilStatus;
}
}
Ce décorateur, en effet, crée une propriété appareilName
qu'on peut fixer depuis la balise <app-appareil>
:
class="container"
class="row"
class="col-xs-12"
Mes appareils
class="list-group"
appareilName="Machine à laver"
appareilName="Frigo"
appareilName="Ordinateur"
class="btn btn-success"
[disabled]="!isAuth"
(click)="onAllumer()"Tout allumer
C'est une première étape intéressante, mais ce serait encore plus dynamique de pouvoir passer des variables depuis AppComponent
pour nommer les appareils (on peut imaginer une autre partie de l'application qui récupérerait ces noms depuis un serveur, par exemple). Heureusement, vous savez déjà utiliser le property binding !
Créez d'abord vos trois variables dans AppComponent
:
export class AppComponent {
isAuth = false;
appareilOne = 'Machine à laver';
appareilTwo = 'Frigo';
appareilThree = 'Ordinateur';
constructor() {
Maintenant, utilisez les crochets []
pour lier le contenu de ces variables à la propriété du component :
class="list-group"
[appareilName]="appareilOne"
[appareilName]="appareilTwo"
[appareilName]="appareilThree"
Vous pouvez également créer une propriété pour régler l'état de l'appareil :
export class AppareilComponent implements OnInit {
@Input() appareilName: string;
@Input() appareilStatus: string;
constructor() {
class="list-group"
[appareilName]="appareilOne" [appareilStatus]="'éteint'"
[appareilName]="appareilTwo" [appareilStatus]="'allumé'"
[appareilName]="appareilThree" [appareilStatus]="'éteint'"