Nouvelles Chroniques d'Amethyste

Penser au Sens, pas au Verbe

Angular 1: les bases

Poster un commentaire

Angular (AngularJS) est un de ces frameworks JavaScript que j’aime utiliser et que je recommande fortement. Vous trouverez le site officiel ici:

https://angularjs.org/

 

Angular est un outil de la famille MV*. Son intention est donc de mettre à votre disposition une plomberie qui permet de gérer les interactions entre une vue (une page HTML) et des données basées sur le modèle MVC.

Les interactions cela peut être aussi bien de l’affichage que de la saisie ou du routage…

 

Le point intéressant est que vous n’avez pas à manipuler directement le DOM, c’est Angular qui s’en occupe à votre place et je trouve que c’est un progrès par rapport à JQuery. JQuery est un excellent outil, pas de débat là dessus, mais depuis que j’ai découvert Angular je m’en sers assez peu.

L’autre point est que l’outil est très orienté productivité. Evidemment le prix à payer – et certains le reprochent – est que les coulisses sont un peu boîte noire.

Pour finir Angular est prévu dès l’origine pour être testable. c’est quelque chose d’assez nouveau en JavaScript, mais vu le poids que commencent à prendre le côté client d’une appli, le sujet va se développer forcément.

 

Angular est un framework à opinion. C’est un terme je pense plus précis que dire « un moule » qui embarque par ailleurs une connotation négative que je n’aime pas trop. Un Framework à opinion vous demande donc d’adhérer à une certaine philosophie, des pratiques et des contraintes.

Je voudrai ajouter qu’Angular dispose d’une communauté très riche, il est très bien bordé par la littérature et c’est Google qui est à la manœuvre.

Vous ne devriez donc pas prendre un risque technologique fort en l’adoptant.

Au programme:

  • Versions d’Angular
  • Outillage
  • Première page
  • Contrôleurs Angular
  • Le binding
  • Afficher/masquer
  • Autres directives
  • La directive de liste
  • Les filtres
  • Réagir aux événements du modèle
  • Autres syntaxes

 

Pour finir vous trouverez sur Plnker un projet de démonstration qui reprend et approfondit un peu les notions présentées dans cet article:

http://plnkr.co/edit/ZjjM6f97MQOwH0VOff2O

Alors quelle version?

La polémique démarre d’entrée de jeu!

J’ai dis Google, mais en fait pas au début. Angular est un outil avec une histoire longue. En fait Angular n’était pas un Framework MVC à ses débuts.

Donc plusieurs évolutions ont déjà marquées son histoire et la dernière en date (Angular 2) est en route et aura une compatibilité « difficile » avec la précédente. C’est pour cela que ce tutoriel cible spécifiquement Angular 1.

Les exemples de cet articles sont testés avec la version 1.5.2.

Outillage

Côté navigateur, j’utilise Chrome et je conseille d’installer Batarang:

https://chrome.google.com/webstore/detail/angularjs-batarang/ighdmehidhipcmcojjgiloacoafjmpfk

Ce plugin est bien pratique pour déboguer une application Angular. D’autres plugins similaires existent, ils font tous plus ou moins la même chose.

Première page Angular

La démo la plus simple possible en Angular. Pas très utile en apparence, mais qui va tout de même nous enseigner beaucoup de choses:

<!DOCTYPE html>
<html ng-app="MonApplication">
<head>
   <title></title>
   <script src="./lib/angular/angular.min.js"></script>
   <script>
      angular.module("MonApplication", []);
   </script>
</head>
 
<body>
   <h1>Ma première appli!</h1>
   Résultat: {{1+2}}
</body>

Qui doit afficher ceci:

2016-03-25_10-17-46

Regardez bien ce code. Tout d’abord la balise html s’est enrichit d’un nouvel attribut: ng-app.

Cet attribut peut apparaître dans la balise de contenu de votre choix, html est le cas le plus fréquent. Elle désigne le bloc de code HTML dont le DOM sera surveillé et pris en charge par Angular. Donc en dehors de ce bloc, Angular ne fonctionnera pas. Le paramètre est le module principal de la page.

On ne peut en avoir qu’un seul par page, c’est pour cette raison que très souvent on le pose sur <html>.

 

Notez aussi le préfixe: ng-.

La philosophie d’Angular est là: enrichir la sémantique HTML avec des attributs de la forme ng-*. Ces attributs s’appellent des directives. Une directive est un outil Angular permettant d’étendre la sémantique HTML. Il existe 3 types de directives:

  1. Les balises
    <ng-form/>
  2. Les attributs
    ng-app
  3. Les classes
    <div class= »ng-form »/>

Certaines directives existent sous plusieurs formes comme ng-form, d’autres non. Angular en propose toute une collection, mais on peut développer les nôtres ce qui donne toute sa puissance à Angular.

 

Un peu plus loin on rencontre une expression Angular (doubles moustaches):

{{1+2}}

Les doubles moustaches déclarent une expression que le moteur Angular doit évaluer. Il s’agit en fait d’une alternative à la directive ng-bind. Je parlerai du binding plus tard. Fonctionnellement, le binding déclare une zone où Angular va exécuter une action donnée, ici additionner 1 et 3.

 

Entre les deux une ligne importante:


angular.module("MonApplication", []);

Angular désigne le point d’entrée du Framework lui-même. Il s’agit d’un module JavaScript à l’intérieur duquel nous allons déclarer tout notre code métier.

Un module Angular est un conteneur dans lequel on va déclarer comme des contrôleurs, des filtres, des directives… bref tout le code nécessaire à notre application. L’avantage d’utiliser un module est que celui-ci ne va pas polluer le scope global. La fonction module nous sert à initialiser notre application dont on passera le nom dans le premier paramètre. Le paramètre suivant est un tableau vide qui peut servir à injecter des dépendances et en particulier d’autres modules.

Prenons un exemple. Le module Sanitize est très fréquemment utilisé. Côté Bower cette déclaration suffit:

« angular-sanitize »: « 1.5.3 »

Une fois le module référencé dans l’application on doit l’injecter dans Angular:


<script src="./lib/angular-sanitize/angular-sanitize.js"></script>

Et du côté JavaScript:

angular.module("MonApplication", ["ngSanitize"])

 

Cette dépendance fonctionne exactement comme l’ajout d’une référence dans un projet C#. L’espace de noms ngSanitize devient accessible dans notre code, il sera interprété correctement par le moteur Angular.

 

La syntaxe avec deux paramètres que nous venons de voir est une syntaxe de setter. Elle sert à créer le module et écrase celui du même nom qui pourrait déjà exister.

La syntaxe de getter (lecture) est la suivante:


angular.module("MonApplication");

Il n’y a qu’un seul paramètre.

On a vu comment déclarer le module principal de la page, une page peut parfaitement en avoir plusieurs qui seront dédiés à des tâches particulières. On les injecte dans le deuxième paramètre de la syntaxe de setter. Je présenterai un exemple dans un autre tutoriel.

 

On a déjà mis en place pas mal de choses, il est temps d’intégrer un autre larron : le contrôleur.

Contrôleurs Angular

Un contrôleur Angular est une fonction qui attend un service de scope en paramètre. En fait c’est plus précis que cela. Le scope est une catégorie particulière de fonction Angular que l’on appelle un service. C’est via les paramètres du contrôleur que l’on injecte les différents services dont il peut avoir besoin. Dans le cadre de cet article $scope nous suffira.

 

La première responsabilité du contrôleur est d’initialiser le $scope. Une vue ne peux pas accéder au modèle s’il n’est pas déclaré dans le scope.

Il ne faut pas confondre le modèle et le scope, ce dernier est juste le conteneur dans lequel on va déclarer le modèle.

Découverte

Examinons un exemple:

<script>
function MonControlleur($scope) {
   $scope.user = { name: "Paul", lastName: "Ballard" }
   $scope.title = "Tutoriel Angular";
}
 
angular.module("MonApplication", [])
   .controller("MonControlleur", MonControlleur);
</script>

J’ai déclaré un contrôleur appelé MonControlleur. Le contrôleur n’instancie pas $scope, mais il lui est injecté en paramètre. On reconnaît là un des patterns d’inversion de contrôle appelé ‘injection de dépendance’.

Regardez surtout la façon dont $scope est utilisé. C’est important, tout ce qui sera déclaré sous $scope pourra apparaître dans le template de vue.

On injecte le contrôleur dans le module à l’aide de la méthode controller(). Le nom proposé peut être différent de celui de la fonction elle-même.

Tournons nous vers le template justement:


<body ng-controller="MonControlleur">
   <h1>Ma première appli!</h1>
   Titre: {{title}}
</body>

Une nouvelle directive ng-controller est utilisée. Son paramètre est bien entendu le nom donné au contrôleur qui va gérer le bloc HTML concerné, ici <body>.

Tous les objets du scope sont disponibles et l’affichage est celui-ci:

2016-03-25_18-43-24

Hiérarchie des scopes

Il est possible de déclarer plusieurs contrôleurs et se pose alors l’intéressante question de la hiérarchie des scopes. Regardez cet exemple:

function MonControlleur($scope) {
   $scope.title = "Tutoriel Angular";
}
 
function AutreControlleur($scope) {
   $scope.title = "Titre de la page";
}
 
angular.module("MonApplication", [])
   .controller("MonControlleur", MonControlleur)
   .controller("AutreControlleur",AutreControlleur);

Un deuxième contrôleur appelé AutreControlleur a été ajouté. Pour l’associer au module il suffit d’utiliser la méthode controller() comme pour son copain. Remarquez que AutreController redéfinit la valeur de la propriété title. Que va t’il se passer?

 

Faisons l’essai suivant pour clarifier le point:

<body ng-controller="MonControlleur">
   <h1>Ma première appli!</h1>
   Titre: {{title}}<br />
 
   <p ng-controller="AutreControlleur">
      Titre: {{title}}<br/>
      Titre: {{$parent.title}}
   </p>
</body>

Remarquez que les deux déclarations ng-controller sont imbriquées et non pas côte à côte. Voyons tout de suite l’affichage:

2016-03-25_18-55-47

La ligne 3 est exécutée dans la portée de MonController. La valeur de title est donc celle assignée par ce contrôleur. Pas de surprise.

A partir de la ligne 5 on entre dans le domaine de AutreControlleur qui se superpose à celui de MonControlleur.

Le raisonnement est le même et c’est pourquoi on voit apparaître ‘Titre de la page’ et non pas ‘Tutoriel Angular’.

Angular gère les scopes de façon hiérarchique. Lorsqu’il a besoin de résoudre une référence il remonte les scopes jusqu’à trouver un qui définit cette référence. Il s’arrête là. J’ai dis Angular, mais c’est en fait le comportement standard de JavaScript vis à vis des scopes. Rien de nouveau.

 

On peut préciser les choses à la ligne 7 qui introduit le service $parent qui renvoi le scope parent, celui de MonControlleur ici. On retrouve logiquement l’affichage de la ligne 3.

Voici ce que l’on observerait avec Batarang:

2016-03-28_11-16-02

On observe bien 3 scopes imbriqués. Le premier est celui du module, il s’appelle $rootScope. Chaque contrôleur a son propre scope qui est un enfant de $rootScope en dernier ressort.

On observe également que le scope de AutreControlleur est aussi un descendant de celui de MonControlleur.

Si l’on avait juxtaposé les contrôleurs de la façon suivante:


<body>
<div ng-controller="MonControlleur">
   <h1>Ma première appli!</h1>
   Titre: {{title}}<br />
</div>
<p ng-controller="AutreControlleur">
   Titre: {{title}}<br/>
</p>
</body>

On aurait observé ceci:

2016-03-28_11-22-26

Les scopes ne sont pas hiérarchisés, mais restent des enfants de $rootScope.

 

Remarquez aussi un point crucial sur lequel je reviendrai dans un prochain article sur les services: le contrôleur n’existe qu’au moment de la déclaration ng-controler. Il est construit dynamiquement. On n’a donc pas de notion de persistance dans les contrôleurs.

Scope et formulaire

Les formulaires sont déclarés à l’aide de la balise <form>. Le problème avec cette balise est qu’il n’est pas possible de les imbriquer, mais pour cela on dispose de la directive ng-form.

 

Si on ajoute une propriété name à <form> alors on va l’ajouter automatiquement au scope.

Regardons un peu cet exemple:

<!DOCTYPE html>
<html ng-app="MonApplication">
<head>
<title></title>
<script src="./lib/angular/angular.min.js"></script>
<script>
   function MonControlleur($scope) {
   }
   angular.module("MonApplication", [])
      .controller("MonControlleur", MonControlleur);
</script>
</head>
 
<body ng-controller="MonControlleur">
   <form name="form">
   </form>
</body>
</html>

 

Du côté de Batarang on voit ceci:

2016-03-28_20-08-00

Une propriété form est accessible depuis le scope du contrôleur. Les différentes variables qui apparaissent comme $valid, sont détaillées ici:

http://tutorials.jenkov.com/angularjs/forms.html

 

Ajoutons un modèle:

<body ng-controller="MonControlleur">
<form name="form">
   <input name="prenom" ng-model="user.prenom" />
</form>
 
<h1>Prénom: {{form.prenom}}</h1>
<h1>User.Prenom: {{user.prenom}}</h1>
</body>

Qui affiche ceci:

2016-03-28_20-33-57

Les points importants pour que ça marche:

  • Mettre un attribut name à <form> et <input>
  • Ajouter une déclaration ng-model
  • La première syntaxe renvoi l’objet du scope
  • La deuxième syntaxe renvoi au modèle

Côté Batarang le scope ressemble à ceci:

2016-03-28_20-37-24

Rendez vous un peu plus loin lors de l’analyse du binding two way pour une démonstration plus élaborées de ces idées.

Le binding

Le binding est une des fonctionnalités phares d’Angular. Le binding fournit un moyen de dialoguer entre la vue et le modèle. L’idée est ancienne, ceux qui ont fait du WPF connaissent déjà.

Angular gère 3 mécanismes:

  1. Binding one way
    Les modifications des données par le modèle sont immédiatement répercutée sur la vue, mais la vue ne peut pas transmettre de modifications au modèle
  2. Binding two way
    Le modèle aussi bien que la vue peuvent transmettre des modifications
  3. Binding one time
    Le modèle transmet l’état des données à la vue au moment de l’initialisation, puis ferme le canal de communication

One way

La syntaxe {{…}} déjà rencontrée est associée au binding one way. Le problème de cette syntaxe est qu’entre le moment où la page se charge et celui où Angular agit, il s’écoule un léger écart pendant lequel on peut voir apparaître les moustaches.

Ce n’est pas bien grave, mais pas très pro. On peut donc préférer la directive ng-binding qui elle ne s’activera que lorsqu’Angular sera prêt:


Résultat: <div ng-bind="1+2"></div>

 

Je parlerai plus loin de ng-cloak.

 

Plusieurs variantes existent comme ng-bind-template qui effectue le binding sur un template fournit. Contrairement à ng-bind, ng-bind-template supporte plusieurs expressions {{..}}. Par exemple:


<h1 ng-bind-template="{{nom}} - {{prenom}}"></h1>

Qui affiche:

Ballard – Paul

 

On peut aussi sortir localement un bloc HTML de la portée d’Angular avec ng-non-bindable. Par exemple:


<div ng-non-bindable>{{1+3}}</div>

Affiche:

{{1+3}}

Et non pas 4.

Two way

Pour faire du binding two way on a besoin de déclarer un modèle avec la directive ng-model:

<!DOCTYPE html>
<html ng-app="MonApplication">
<head>
   <title></title>
   <script src="./lib/angular/angular.min.js"></script>
   <script>
      function MonControlleur($scope) {
         $scope.nom = "Ballard";
         $scope.prenom = "Paul";
      }
 
      angular.module("MonApplication", []).controller("MonControlleur", MonControlleur);
   </script>
</head>
 
<body ng-controller="MonControlleur">
<h1>{{nom}} {{prenom}}</h1>
Nom: <input ng-model="nom" value="{{nom}}" /><br/>
Prénom: <input ng-model="prenom" value="{{prenom}}"/>
</body>

Cet exemple résume tout ce qu’il y a à connaître. Le contrôleur initialise deux paramètres dans son scope: nom et prenom. On les affiche dans le titre <h1>.

On peut aussi les éditer dans des balises <input>.

Au démarrage on constate que les zones de saisies et d’affichage exposent les valeurs initiales:

2016-03-25_20-04-46

 

C’est le comportement one way déjà vu. Modifions par exemple le prénom:

2016-03-25_20-06-03

Immédiatement le titre se met à jour. Cela démontre que les modifications apportées par la vue sont redescendues dans le modèle. C’est extrêmement simple comparé à d’autres méthodes. C’est pour cela qu’il est important d’outiller ce genre d’action, par exemple avec Angular. On gagne énormément de temps.

Remarquez également que les choses se font sans aller-retour serveur.

 

On peut intervenir sur la façon dont la mise à jour s’effectue à l’aide de la directive ng-model-options. Celle-ci attend un objet qui lui fournit divers renseignements à travers 3 propriétés facultatives:

  1. updateOn
    Sur quel événement la mise à jour est effectuée
  2. debounce
    Les mise à jour sont transmises après un délai précisé en millisecondes
  3. getterSetter
    activer le mode getter/setter. Attend un booléen

 

Typiquement on va choisir de transmettre la modification du modèle au moment où la zone de saisie perd le focus:


<input ng-model-options="{updateOn:'blur'}" ng-model="nom" value="{{nom}}" />

Ce que je vous laisse vérifier.

 

Le mode getterSetter est intéressant lorsque la propriété du modèle est plus qu’une simple propriété passive, c’est à dire que l’on a besoin de l’associer à une fonction plutôt qu’une propriété.

<!DOCTYPE html>
<html ng-app="MonApplication">
<head>
<title></title>
<script src="./lib/angular/angular.min.js"></script>
<script>
function MonControlleur($scope) {
   var _nom = "Ballard";
   $scope.nom = function(name) {
      if (angular.isDefined(name)) {
            _nom = name;
         }
 
      return _nom;
};
 
   $scope.prenom = "Paul";
}
 
angular.module("MonApplication", []).controller("MonControlleur", MonControlleur);
</script>
</head>
 
<body ng-controller="MonControlleur">
<h1>{{nom()}} {{prenom}}</h1>
Nom: <input ng-model-options="{getterSetter:true}" ng-model="nom" value="{{nom}}" /><br/>
Prénom: <input ng-model="prenom" value="{{prenom}}"/>
</body>

Observez comment est définit la propriété nom, par une fonction. Si une valeur est fournit, il s’agit d’un setter. Le paramètre privé _nom est mis à jour et la nouvelle valeur est renvoyée.

Si aucun paramètre est fournit, on a affaire à un getter, _nom est renvoyé.

 

Dans la vie courante on ne va pas créer autant de propriétés qu’il y a de zones de saisies dans le scope, on va plutôt se servir d’un objet. Les choses vont ressembler à ceci:

<!DOCTYPE html>
<html ng-app="MonApplication">
<head>
<title></title>
<script src="./lib/angular/angular.min.js"></script>
<script>
   function MonControlleur($scope) {
      $scope.add = function (animal) {
         $scope.animaux.push(animal);
      };
 
      $scope.animaux = [{ nom: "Chien", nbPattes: 4 }, { nom: "Chat", nbPattes: 4 }, { nom: "Poisson", nbPattes: 0 }, { nom: "Oiseau", nbPattes: 2 }];
   }
 
   angular.module("MonApplication",[]).controller("MonControlleur", MonControlleur);
</script>
</head>
 
<body ng-controller="MonControlleur">
<ul>
   <li ng-repeat="animal in animaux">{{animal.nom}}</li>
</ul>
 
<form>
   <input placeholder="nom animal" ng-model="animal.nom" />
   <input placeholder="nb de pattes" ng-model="animal.nbPattes"/>
 
   <input type="submit" value="Ajouter" ng-click="add(animal)"/>
</form>
</body>
</html>

 

Le contrôleur initialise une liste d’animaux connus par leurs nom et nombre de pattes. On affiche la liste à l’aide de la directive ng-repeat.

Sous la liste on dispose d’un formulaire de saisie qui permet de créer un nouvel animal. Un Submit ajoute animal au modèle et met automatiquement à jour la liste.

2016-03-28_17-36-04

La clef du fonctionnement est comme tout à l’heure ng-model. Mais cette fois on désigne non pas directement une propriété du scope, mais la propriété d’un objet animal qui sera construit dynamiquement pour être passé à la fonction add() déclarée dans ng-click sur le bouton de soumission. La méthode Add() reçoit une nouvelle instance de animal et l’ajoute à la liste animaux.

C’est extrêmement simple, imaginez la masse de code à faire par d’autres moyens.

 

On peut vouloir faire un peu de validation. L’attribut HTML 5 required fonctionne:


<input placeholder="nom animal" ng-model="animal.nom" type="text" required />
<input placeholder="nb de pattes" ng-model="animal.nbPattes" required />

Note: required a besoin d’être dans un <form> pour fonctionner. Le reste est moins exigeant.

 

Si votre code n’est pas du HTML 5, on peut comme alternative se servir d’une directive ng-required:


<input placeholder="nom animal" ng-model="animal.nom" type="text" ng-required="true" />
<input placeholder="nb de pattes" ng-model="animal.nbPattes" ng-required="trues" />

 

Est t’il possible de contrôler que la saisie du nombre de pattes soit un nombre? Une première idée est d’ajouter le type:


<input name="nbPattes" placeholder="nb de pattes" ng-model="animal.nbPattes" required type="number" />

Seule la saisie de nombre sera autorisée. Mais en l’état on voudrait limiter la saisie à un nombre limité de digits. On a plutôt besoin d’une expression régulière.

La directive ng-pattern va nous y aider. Cette directive attend une expression régulière. Voici un exemple:

<!DOCTYPE html>
<html ng-app="MonApplication">
<head>
<title></title>
<script src="./lib/angular/angular.min.js"></script>
<script>
   function MonControlleur($scope) {
      $scope.regex = '\\d{1,2}';
 
      $scope.add = function (animal) {
            $scope.animaux.push(animal);
   };
 
   $scope.animaux = [{ nom: "Chien", nbPattes: 4 }, { nom: "Chat", nbPattes: 4 }, { nom: "Poisson", nbPattes: 0 }, { nom: "Oiseau", nbPattes: 2 }];
}
 
   angular.module("MonApplication",[]).controller("MonControlleur", MonControlleur);
</script>
</head>
 
<body ng-controller="MonControlleur">
<ul>
   <li ng-repeat="animal in animaux">{{animal.nom}}</li>
</ul>
 
<form name="form">
   <input placeholder="nom animal" ng-model="animal.nom" type="text" required />
   <input name="nbPattes" placeholder="nb de pattes" ng-model="animal.nbPattes" required ng-pattern="regex" type="number" />
 
   <input type="submit" value="Ajouter" ng-click="add(animal)" />
</form>
</body>
</html>

 

La discussion de la syntaxe a été faite au chapitre ‘Ajouter un formulaire dans le scope’. Ce qui m’intéresse est l’introduction de ng-pattern qui reçoit soit directement l’expression régulière, soit comme ici un élément du modèle qui contient cette expression.

 

Le problème est que même si la validation échoue, un ajout est tout de même fait au modèle. On peut le bloquer en modifiant Add() ainsi:


$scope.add = function (animal) {
   if ($scope.form.nbPattes.$valid) {
      $scope.animaux.push(animal);
   }
};

 

Ce qui nous intéresse est le contenu de la méthode Add() dans le contrôleur qui exploite $valid. La valeur est true lorsque le formulaire est entièrement validée.

Et on complète en rendant par exemple invalide le bouton tant que le formulaire n’est pas correctement renseigné:

<input type="submit" value="Ajouter" ng-click="add(animal)" ng-disabled="form.nbPattes.$invalid" />

On pourrait aussi rendre visible un message explicatif avec ng-show.

 

Voici un moyen de valider correctement un formulaire. Angular ne change rien aux fondamentaux, la validation doit aussi se faire côté contrôleur.

One time

Le binding one time dispose d’une syntaxe spécifique:


<h1>{{::nom()}} {{::prenom}}</h1>

Je vous laisse vérifier que même si je modifie une valeur dans les zones de saisie, celle-ci n’est pas renvoyée vers le modèle.

One time doit être envisager si la donnée est statique. La raison déborde du cadre de cet article, mais cela améliore les performances du cycle digest.

Syntaxe complexe

Il est important de savoir que le moteur d’évaluation des expressions Angular peut évaluer des expressions plus complexes qu’une simple propriété. On pourrait réécrire le modèle ainsi:

<!DOCTYPE html>
<html ng-app="MonApplication">
<head>
<title></title>
<script src="./lib/angular/angular.min.js"></script>
<script>
   function MonControlleur($scope) {
      $scope.client = {nom: "Ballard", prenom: "Paul"};
   }
 
   angular.module("MonApplication", []).controller("MonControlleur", MonControlleur);
</script>
</head>
 
<body ng-controller="MonControlleur">
<h1>{{client.nom}} {{client.prenom}}</h1>
<input id="nom" ng-model="client.nom"/>
<input id="nom" ng-model="client.prenom"/>
</body>

A la place de propriétés on fournit un objet appelé client dans le scope. Pour accéder à ses propriétés nom et prenom, Angular devra évaluer la syntaxe en dot (.), ce qu’il parvient à faire sans encombres:

2016-03-28_11-02-08

Afficher/masquer

Plusieurs directives peuvent servir selon le besoin:

  1. ng-show
    Affiche un bloc html
  2. ng-hide
    Masque un bloc html
  3. ng-if
    Implémente un if
  4. ng-switch
    Equivalent du switch en C#

Elles attendent un booléen comme paramètre. Elles ne sont pas particulièrement difficiles à utiliser, voici un exemple:

<!DOCTYPE html>
<html ng-app="MonApplication">
<head>
<title></title>
<script src="./lib/angular/angular.min.js"></script>
<script>
   function MonControlleur($scope) {
      $scope.nom = "Ballard";
      $scope.prenom = "Paul";
      $scope.age = 50;
   }
 
  angular.module("MonApplication", []).controller("MonControlleur", MonControlleur);
</script>
</head>
 
<body ng-controller="MonControlleur">
<h1>{{nom}} {{prenom}}</h1>
Age: <input ng-model="age" value="{{age}}" /><br/>
 
<div ng-if="age < 18">
Vous êtes mineur!!!
</body>

Initialement il s’affiche:

2016-03-25_22-19-39

Saisissez par exemple 15:

2016-03-25_22-20-27

Autres directives

Certaines directives semblent directement l’équivalent d’attribut html natifs:

  • ng-href
  • ng-submit
  • ng-click

Je ne vais pas les détailler, le nom suffit. Quel est leur intérêt?

 

On pourrait utiliser l’attribut natif, ça fonctionne, mais pas toujours bien lorsqu’ils se rapportent à un élément géré par Angular.  Par exemple le lien de ng-href pourrait être construit dynamiquement par binding.

Le problème est qu’il y a une possibilité que le client clique dessus avant qu’Angular ait pu s’activer. Le lien ne fonctionnera alors évidemment pas.

Utiliser la directive dans ce cas apporte une sécurité.

 

La directive ng-cloak est un bon exemple de ce comportement. Elle permet de masquer un bloc html jusqu’à ce qu’Angular puisse démarrer et traiter les directives et expressions présentes dans le bloc.

Ajoutons la directive à la balise body:


<body ng-cloak ng-controller="MonControlleur">

Ng-cloak fonctionne en masquant temporairement le code HTML via un bloc de CSS standard que l’on doit absolument ajouter:


[ng\:cloak], [ng-cloak], [data-ng-cloak], [x-ng-cloak], .ng-cloak, .x-ng-cloak {
display: none !important;
}

Le style fait partie d’Angular, il n’y a rien de particulier à faire à notre niveau si ce n’est l’ajouter.

C’est une façon de masquer la plomberie Angular en attendant que le DOM ait terminé son chargement et que le moteur Angular puisse faire son travail.

 

Le code de démo place le chargement d’Angular au début de la page ce qui est aussi une façon de résoudre le problème. Cette méthode n’est toutefois pas considéré comme une bonne pratique. Si vous souhaitez lire une analyse plus complète de ng-cloak:

http://weblog.west-wind.com/posts/2014/Jun/02/AngularJs-ngcloak-Problems-on-large-Pages

La directive de liste

La directive de liste ng-repeat est évidemment présente dans presque toutes les applications. Une démonstration est la façon la plus efficace de la découvrir.

<!DOCTYPE html>
<html ng-app="MonApplication">
<head>
<title></title>
<script src="./lib/angular/angular.min.js"></script>
<script>
   function MonControlleur($scope) {
      $scope.nombres = ['Un','Deux','Trois','Quatre','Cinq'];
   }
 
   angular.module("MonApplication", []).controller("MonControlleur", MonControlleur);
</script>
</head>
 
<body>
<ul ng-controller="MonControlleur">
   <li ng-repeat="nombre in nombres">
      {{nombre}}
   </li>
</ul>
</body>

Qui affiche:

2016-03-25_22-34-58

La directive ng-repeat implémente une boucle for. Pour chaque élément de la liste une instance du template sera fournie. Il existe différentes options de filtrage ou de tri, je vous renvoi à la documentation.

Il est intéressant de voir comment Angular traite cette liste avec Batarang:

2016-03-30_08-58-40

Chaque élément de la liste apparaît donc dans le scope avec des information indiquant leur position dans la liste (premier, dernier, pair…).

Ces information sont importantes car un filtre peut parfaitement intervenir sur l’ordre d’affichage des éléments. J’anticipe sur le chapitre qui suit, mais ce code:


<ul>
   <li ng-repeat="nombre in nombres | orderBy : 'toString()'">
      {{nombre}}
   </li>
</ul>

Affiche ceci:

2016-03-30_09-13-27

Ces propriétés sont dans le scope, donc accessibles. On peut donc les utiliser comme par exemple:


<ul>
   <li ng-repeat="nombre in nombres">
      {{nombre}} (id: {{$id}})
   </li>
</ul>

Qui nous donne:

2016-03-30_09-17-58

Le service $id renvoi toujours l’id du scope en cours. Par exemple si j’ajoute ceci dans le body:

{{$id}}

Je vois s’afficher ‘2’, parce que c’est l’id du contrôleur:

2016-03-30_09-20-30

Ng-repeat est souvent associée à deux autres directives qui complètent ng-class:

  1. ng-class-even
  2. ng-class-odd

On peut ainsi déclarer une classe CSS qui s’applique aux lignes paires ou impaires.

 

L’appli de démo lié à cet article présente des exemples plus sophistiqués avec en particulier des listes déroulantes.

Les filtres

Les filtres sont utilisées pour 4 raisons:

  1. Modifier l’affichage d’une propriété du modèle
  2. Formater l’affichage
  3. Trier un jeu de données
  4. Filtrer un jeu de données

La syntaxe est la suivante:

{{ expression | filtre<:paramètre> }}

Regardons quelques exemples:


<div>{{1.2345 | number:1}}</div>
<div>{{'Amethyste' | uppercase}}</div>

Qui produit cet affichage:

2016-03-28_16-01-23

Un filtre peut donc avoir des paramètres qui sont introduits avec le séparateur : (deux points).

Je pense qu’orderBy est sans doute le filtre le plus utilisé dans une application. Regardons le en action sur cet exemple:

<!DOCTYPE html>
<html ng-app="MonApplication">
<head>
<title></title>
<script src="./lib/angular/angular.min.js"></script>
<script>
   function MonControlleur($scope) {
      $scope.countries =  [{nom:"France"},{nom:"Espagne"}, {nom:"Italie"}, {nom:"Pays-Bas"}];
   }
 
   angular.module("MonApplication",[]).controller("MonControlleur", MonControlleur);
</script>
</head>
 
<body ng-controller="MonControlleur">
<ul>
   <li ng-repeat="country in countries">{{country.nom}}</li>
</ul>
</body>
</html>

Qui nous affiche une liste de pays dans l’ordre où ils sont reçus:

2016-03-28_16-09-29

On pourrait choisir un ordre alphabétique:


<ul>
   <li ng-repeat="country in countries | orderBy:'nom'">{{country.nom}}</li>
</ul>

2016-03-28_16-17-58

Pour que ça fonctionne n’oubliez pas de déclarer le nom du paramètre comme une chaîne, donc avec des quotes ou des doubles quotes. On pourrait inverser l’ordre ainsi:


<ul>
   <li ng-repeat="country in countries | orderBy:'-nom'">{{country.nom}}</li>
</ul>

Un simple tiret devant le nom du paramètre.

En général le nom du paramètre de tri et le sens du tri sont définis dynamiquement par le client de la page. On a juste besoin de remplacer le nom de la propriété en dur par un paramètre du scope.

Côté contrôleur on ajoute ceci:


$scope.predicat = '-nom';

Et le code HTML:


<ul>
   <li ng-repeat="country in countries | orderBy:predicat">{{country.nom}}</li>
</ul>

Il ne reste plus qu’à rendre sélectionnable la valeur de predicat par le client selon une des méthodes déjà vues. On trouve plusieurs exemples dans la documentation:

https://docs.angularjs.org/api/ng/filter/orderBy#!

 

Examinons un dernier filtre: filter. Comme son nom l’indique ce filtre filtre la sortie par rapport à un certain critère. Par exemple si on s’intéresse aux pays dont le nom contient la lettre ‘s’:


<ul>
  <li ng-repeat="country in countries | filter:'s'">{{country.nom}}</li>
</ul>

2016-03-28_16-39-53

 

Si la liste des filtres standards ne vous suffit pas, il est toujours possible d’écrire ses propres filtres.

L’appli de démo liée au site présente une démonstration de filtre multiple.

Réagir aux changements de modèle

Les événements sont un des moyens de réagir au changement du modèle.

J’ai déjà évoqué ng-submit et ng-click, je vais plutôt présenter ng-change qui fonctionne de façon identique. La directive est utilisée pour déclencher un événement lorsqu’une valeur change:

<!DOCTYPE html>
<html ng-app="MonApplication">
<head>
<title></title>
<script src="./lib/angular/angular.min.js"></script>
<script>
   function MonControlleur($scope) {
      $scope.nom = "";
 
      $scope.nomChange = function () {
         if ($scope.nom) {
            $scope.messageBienvenue = "Vous avez saisi " + $scope.nom.length + " lettres";
         }
         else {
            $scope.messageBienvenue = "Pas de saisie";
         }
      };
   }
 
angular.module("MonApplication", []).controller("MonControlleur", MonControlleur);
</script>
</head>
 
<body ng-controller="MonControlleur">
<h1>{{messageBienvenue}}</h1>
<input ng-model="nom" ng-change="nomChange()"/>
</body>

L’affichage ressemble à ceci:

2016-03-25_22-55-24

Examinons ce code.

Le contrôleur déclare une fonction nomChange() qui est le paramètre passé à ng-change. cette méthode sera invoquée à chaque modification du nom dans la zone de saisie.

La méthode elle-même alimente une propriété messageBienvenue dans le scope. C’est le texte affiché dynamiquement en gras.

Note: il est important de noter que ng-change a besoin de la présence de ng-model pour fonctionner.

Autres syntaxes

J’ai présenté diverses démos, mais il s’agit de démo. J’ai donc plus besoin d’un code facile à lire que d’un code bien structuré.

On va donc parler un peu de cela. Je pense qu’il est maintenant acquis par tous les développeurs qu’un truc du style:

<script>
function MonControlleur($scope) {
}
 
   angular.module("MonApplication",[]).controller("MonControlleur", MonControlleur);
</script>

N’a plus sa place dans un développement moderne. Le JavaScript doit se trouver le plus possible dans un fichier dédié.

 

Une fois ce travail effectué examinez ce code. J’y trouve au moins 2 responsabilités:

  1. initialiser le module et le scope racine
  2. définir le(s) contrôleur(s)

Deux responsabilités, la bonne pratique est de les détacher clairement l’une de l’autre. On dispose de plusieurs moyens. Le plus simple:

var app = angular.module("monApplication",[]);
 
function monControlleur($scope) {
}
 
app.controller("monControlleur", monControlleur);

On sépare l’initialisation de l’application Angular de sa configuration. Je trouve d’ailleurs le code tout de suite plus lisible. Une amélioration notable a même été apportée: se conformer aux règles de nommages JavaScript!

 

 

Dans le cas où votre application est relativement simple je crois que c’est suffisant. Pour des applications plus complexes avec plusieurs modules, beaucoup de contrôleur, de services… on a besoin d’un degré supplémentaire de structuration. Je pense que pour ce type d’application il est préférable d’éviter l’amateurisme en bricolant son propre guide de développement qui sera plus ou moins bien diffusé et suivit.

 

Google a produit un guide de développement et de bonnes pratiques:

https://google.github.io/styleguide/angularjs-google-style.html

 

Plusieurs experts réputés ont soulignés quelques faiblesses. C’est pourquoi ils proposent leurs propres guides qui se recoupent en partie. Le mieux est peut être de vous faire votre opinion.

  • Celui de Todd Motto:

https://github.com/toddmotto/angular-styleguide

  • Celui de John Papa dont la version en français est ici:

https://github.com/johnpapa/angular-styleguide/blob/master/a1/i18n/fr-FR.md

L’intérêt est qu’ils ne se contentent pas de lister des règles, mais ils expliquent pourquoi elles leurs semblent pertinentes. C’est cela qui les transforme en outil de travail. Et puis c’est toujours riche d’enseignement lorsque l’on débute d’écouter la voix des experts.

Publicités

Laisser un commentaire

Entrez vos coordonnées ci-dessous ou cliquez sur une icône pour vous connecter:

Logo WordPress.com

Vous commentez à l'aide de votre compte WordPress.com. Déconnexion / Changer )

Image Twitter

Vous commentez à l'aide de votre compte Twitter. Déconnexion / Changer )

Photo Facebook

Vous commentez à l'aide de votre compte Facebook. Déconnexion / Changer )

Photo Google+

Vous commentez à l'aide de votre compte Google+. Déconnexion / Changer )

Connexion à %s