Angular2 : les liaisons

Mis à jour pour la version rc1 !

Dans le précédent article on a vu comment mettre en place une infrastructure de développement et on a aussi créé une première application sommaire qui se contentait d’afficher un titre préétabli dans la page HTML. Dans le présent article on va voir comment établir une liaison entre un composant et la page HTML de telle manière qu’une modification au niveau des données du composant se répercute sur la page, et réciproquement.

Pour cet article on va repartir de la base créée dans le précédent.

Pour vous simplifier la vie le code final est téléchargeable ici.

Afficher des données

On va s’intéresser en premier à l’affichage des données présentes dans le composant en direction de la page HTML.

L’interpolation

C’est l’appellation choisie pour la syntaxe « mustache » qui  est devenu une référence pour le templating dans de multiples langages. C’est d’ailleurs souvent un souci à cause de conflits potentiels (par exemple avec Blade de Laravel). Cette syntaxe consiste à utiliser des accolades doubles. Modifions un peu le composant pour montrer cela :

import {Component} from '@angular/core';

@Component({
    selector: 'mon-composant',
    template: '<h1>{{ title }}</h1>'
})
export class AppComponent { 
  title = 'Je suis le titre';
}

Maintenant la classe n’est plus vide, on a créé une propriété et on l’a affectée avec une valeur de type string (on ne précise pas le type parce qu’il est implicitement déterminé par typeScript avec la valeur affectée) :

title = 'Je suis le titre';

Dans le template on prévoit l’emplacement pour afficher la valeur de cette propriété :

template: '<h1>{{ title }}</h1>'

On se retrouve très logiquement avec cet affichage :

img016

Ce qui peut se schématiser ainsi pour bien visualiser la liaison :

img022

On peut difficilement faire plus simple ! Chaque fois que la valeur de la propriété va changer l’affichage dans la vue va également changer.

Au lieu d’affecter directement la propriété on peut très bien le faire dans un constructeur :

export class AppComponent { 
  title: string;
  constructor() {
    this.title = 'Je suis le titre';
  }
}

Avec exactement le même résultat.

Une liste de valeurs

Il arrive souvent qu’on ait à afficher une liste de valeurs, sans savoir exactement combien on en a. Ces valeurs peuvent se trouver dans un tableau.

Un tableau de valeurs

Voici le nouveau code du composant :

import {Component} from '@angular/core';

@Component({
    selector: 'mon-composant',
    template: `
        <h1>{{title}}</h1>
        <ul>
            <li *ngFor="#marque of marques">
                {{marque}}
            </li>
        </ul>
    `
})
export class AppComponent { 
    title = 'Marques :';
    marques = ['Renault', 'Citroen', 'Mercedes', 'Ford'];
}

On a maintenant deux propriétés :

  • title : le titre
  • marques : un tableau de marques de voitures

Le template a aussi changé. J’ai ajouté une liste non ordonnée pour afficher les marques. Notez bien la syntaxe pour itérer dans le tableau :

<li *ngFor="#marque of marques">

On rencontre une première directive : *ngFor (attention à ne pas oublier l’asterisque !). Cette directive est destinée à itérer dans une liste de valeurs, ici un tableau.

Notez aussi la syntaxe de la variable locale #marque, le signe # est destiné à signifier qu’il s’agit bien d’une variable locale à la boucle.

Notez aussi la syntaxe pour pouvoir écrire plusieurs lignes pour le template. On peut aussi mettre le code du template dans un autre fichier et utiliser juste un référence.

Le résultat est maintenant :

img017

Exactement ce qu’on voulait.

On peut le schématiser ainsi :

img023

Un tableau d’objets

Comme TypeScript permet une approche objet classique il serait dommage de s’en priver. Voici le nouveau code :

import {Component} from '@angular/core';

class Marque {
  name: string;
  constructor(name: string) {
    this.name = name;
  } 
}

@Component({
  selector: 'mon-composant',
  template: `
    <h1>{{title}}</h1>
    <ul>
      <li *ngFor="#marque of marques">
        {{ marque.name }}
      </li>
    </ul>
  `
})
export class AppComponent { 
  title = 'Marques :';
  marques = [
    new Marque('Renault'),
    new Marque('Citroen'),
    new Marque('Mercedes'),
    new Marque('Ford')
  ];
}

J’ai créé une classe Marque avec une seule propriété name (par défaut une propriété est publique, on peut donc se passer d’utiliser public) :

class Marque {
  name: string;
  constructor(name: string) {
    this.name = name;
  } 
}

Dans le tableau on met maintenant des objets créés avec la classe Marque :

marques = [
  new Marque('Renault'),
  new Marque('Citroen'),
  new Marque('Mercedes'),
  new Marque('Ford')
];

Il faut modifier un peu le template pour afficher le nom de la marque :

{{ marque.name }}

On utilise évidemment la même directive.

Il n’est pas très judicieux de placer la classe Marque dans le composant. J’ai dit qu’avec Angular on a une approche modulaire et que chaque module doit avoir une tâche bien précise. Le composant n’a ici pas à connaître l’implémentation de la classe Marque mais juste à l’utiliser. Donc pour travailler correctement on doit mettre cette classe dans un fichier spécifique marque.ts :

img019

Avec juste le code de la classe (avec export pour qu’elle soit utilisable par les autres modules) :

export class Marque {
  name: string;
  constructor(name: string) {
    this.name = name;
  } 
}

Et dans le composant on supprime la classe et on ajoute une importation :

import {Component} from '@angular/core';
import {Marque} from './marque'

@Component({
  selector: 'mon-composant',
  template: `
    <h1>{{title}}</h1>
    <ul>
      <li *ngFor="#marque of marques">
        {{ marque.name }}
      </li>
    </ul>
  `
})
export class AppComponent { 
  title = 'Marques :';
  marques = [
    new Marque('Renault'),
    new Marque('Citroen'),
    new Marque('Mercedes'),
    new Marque('Ford')
  ];
}

On pourrait avoir le même raisonnement avec les données des marques mais on verra cet aspect dans un autre article.

Un index

Il arrive qu’on ait besoin d’un index dans une liste. Angular offre cette possibilité qui est facile à mettre en oeuvre :

<li *ngFor="#marque of marques; #i=index">
  {{i + 1}} - {{marque.name}}
</li>

Avec ce résultat :img018

Double liaison

On a vu ci-dessus comment afficher des éléments présents dans le composant. maintenant on va voir la liaison inverse : comment le composant peut recevoir une action de l’utilisateur : clic sur un lien, sur un bouton, entrée d’une information…

On va poursuivre l’exemple ci-dessus de la liste des marques de voitures en permettant d’en ajouter.

Utilisation de la touche « Entrée »

On va ajouter une zone de texte et ajouter la valeur entrée dans la liste des marques lorsqu’on actionne la touche « Entrée ». Voici le nouveau code du composant :

import {Component} from '@angular/core';
import {Marque} from './marque'

@Component({
  selector: 'mon-composant',
  template: `
    <h1>{{title}}</h1>
    <input #newMarque (keyup.enter)="addMarque(newMarque.value)">
    <ul>
      <li *ngFor="#marque of marques">
        {{ marque.name }}
      </li>
    </ul>
  `
})
export class AppComponent { 
  title = 'Marques :';
  marques = [
    new Marque('Renault'),
    new Marque('Citroen'),
    new Marque('Mercedes'),
    new Marque('Ford')
  ];
  addMarque(newMarque: string) {
    if (newMarque) {
      this.marques.push(new Marque(newMarque));
    }
  }
}

On voit apparaître maintenant la zone de texte :

img020

Regardez le code correspondant :

<input #newMarque (keyup.enter)="addMarque(newMarque.value)">

L’événement keyup.enter (en fait un pseudo-événement d’Angular) est placé entre parenthèses. L’action se situe de l’autre côté du signe =. En l’occurrence on appelle une méthode addMarque avec comme paramètre newMarque.value. On a newMarque comme variable locale déclarée dans la balise input, c’est une référence à la balise, c’est pour cette raison qu’on peut récupérer la valeur avec newMarque.value.

Il ne reste alors plus qu’à créer la méthode de traitement dans le composant :

addMarque(newMarque: string) {
  if (newMarque) {
    this.marques.push(new Marque(newMarque));
  }
}

img024

Perte du focus

Il peut être utile aussi de valider l’entrée lorsque la zone de texte perd le focus. Il suffit d’ajouter l’événement blur :

<input #newMarque 
  (keyup.enter)="addMarque(newMarque.value)"
  (blur)="addMarque(newMarque.value)">

Utilisation d’un bouton

Enfin quelque chose de plus classique : l’utilisation d’un bouton. On utilise cette fois l’événement click :

<input #newMarque 
  (keyup.enter)="addMarque(newMarque.value)"
  (blur)="addMarque(newMarque.value)">
<button (click)=addMarque(newMarque.value)>Ajouter</button>

Comme la variable newMarque est locale si on veut effacer le contenu de la zone de texte il faut la réinitialiser après l’appel de la fonction, par exemple :

<button (click)="addMarque(newMarque.value);newMarque.value=''">Ajouter</button>

Dans cet article on a ainsi vu comment établir une interaction à double sens entre la vue et le composant.

 

Laisser un commentaire