Archives de Catégorie: Getting started

Les nouveautés d’AngularJS 1.3 – Part 4

Angular est finalement sorti en version 1.3.0, près d’un an après la release de la 1.2.0. Pas mal de petites nouveautés sont disponibles et vous pouvez voir celles des premières bétas dans les articles précédents :

part 1
part 2
part 3

Cet article est donc le dernier de la série, et nous allons voir quels sont les derniers goodies que les 400 contributeurs (dont @jbnizet et moi-même \o/) ont concoctés.

Validation

Le mécanisme de validation a été légèrement revu dans la version 1.3 et reprend le principe des validateurs de Dart. L’idée est de découpler la validation des champs qui était pour l’instant à la charge des $formatters ou des $parsers. Ceux-ci existent toujours et peuvent transformer les valeurs comme ils le souhaitent, mais c’est maintenant les $validators qui choisissent si une valeur est valide ou non pour le champ, en prenant en compte les différentes directives présentes sur le champ en question (ngMinLength, ngPattern, nos propres directives, etc…).

Cela impacte donc la façon d’écrire une directive de validation custom, pour les rendre plus simples. Par exemple, pour n’autoriser que des logins avec au moins un chiffre :

    app.directive('atLeastOneNumber', function(){
      return {
        require: '?ngModel',
        link: function(scope, element, attributes, modelCtrl){
          modelCtrl.$validators.atLeastOneNumber = function(value){
            return (/.*\d.*/).test(value);
          }
        }
      }
    });

Vous pouvez jouer avec cette nouvelle fonctionnalité ici.

On note aussi l’ajout de validateurs asynchrones. Si vous devez ajouter une validation custom à un champ, en interrogeant votre backend pour savoir si le nom de l’utilisateur est disponible par exemple, alors vous allez pouvoir ajouter votre validateur aux asyncValidators. Ceux-ci ne se lanceront que lorsque les validateurs synchrones auront fini avec succès (par exemple, la taille minimum du login de l’utilisateur).
En plus, une classe ng-pending sera ajoutée au champ, et vous pourrez donc indiquer visuellement que vous attendez une réponse du serveur. En attendant, le champ n’est ni valide, ni invalide.

Si vous voulez voir un exemple détaillé de l’ajout de validateurs (synchrones et asynchrones), c’est sur ce plunkr.

Performance

Il y a eu un gros focus sur la performance, avec pas mal d’optimisations internes et de nouvelles possibilités pour les développeurs. Les références aux benchmarks internes donnent des résultats assez impressionnants.

Vous pouvez maintenant désactiver un certain nombre d’info de debug en prod. Si vous ajoutez cette ligne au démarrage de votre application :

    $compileProvider.debugInfoEnabled(false)

alors Angular ne stockera pas les infos de binding et du scope courant dans le DOM. Les gains sont d’environ 20% sur certains benchmarks utilisés par la team Angular. Attention toutefois, les outils de debug comme Batarang, ou de test comme Protractor ont besoin de ces informations : il faudra donc réactiver les infos de debug pour les tests end-to-end.
Une méthode globale a été ajoutée pour réactiver ces informations à chaud si nécessaire : dans la console du navigateur, entrez angular.reloadWithDebugInfo(). L’appli va alors se recharger, cette fois avec les informations de debug.

Les filtres ont également été revus et optimisés :

    {{ 'prenom' + ' nom' | uppercase }} 

ne sera réévalué que si prénom ou nom change et pas à chaque cycle comme actuellement. Cela considère donc maintenant par défaut que votre filtre est sans état, c’est à dire qu’il ne dépend que des entrées. C’est déjà ce qu’il était conseillé de faire. Si ce n’est pas le cas pour l’un de vos filtres, alors vous devrez marquer le filtre en question comme $stateful. Mais vous comprenez que ce ne doit être qu’un cas exceptionnel.

ngAria

Un autre module fait son apparition : après ngMessages, c’est au tour de ngAria, qui ajoute automatiquement les attributs d’accessibilité sur certaines directives si le module est présent (aria-hidden, aria-checked, aria-disabled, aria-required, aria-invalid, aria-multiline, aria-valuenow, aria-valuemin, aria-valuemax, tabindex). Il est possible de désactiver sélectivement celles que l’on veut.
Plus d’excuses pour ne pas faire d’application Angular accessible !

Vous pouvez bien sûr trouver toutes ces nouveautés et d’autres astuces dans notre ebook à prix libre.

Les nouveautés d’AngularJS 1.3 – Part 3

Nous poursuivons notre exploration des nouvelles features d’AngularJS 1.3!
Pour vous rafraîchir la mémoire, nous avons déjà parlé de ngModelOptions et des nouveaux inputs dans la première partie, ainsi que de ngStrictDI, bindOnce, ngMessages et watchCollection dans la seconde partie.

No more global controllers

Changement particulièrement « BREAKING » ! Par défaut, il n’est désormais plus possible de déclarer un controller comme une fonction globale : tout passera désormais par l’utilisation des modules et de la méthodes d’enregistrement .controller().

Ainsi, ce que vous aviez probablement utilisé dans votre première application de TodoList, ne fonctionnera plus :

    // won't work in 1.3
    function PersonCtrl($scope){
      ...
    }

    // now you must use
    angular.module('controllers')
      .controller('PersonCtrl', function($scope){
        ...
      })

A noter qu’il est possible de réactiver l’ancien comportement en configuration le $controllerProvider avec la méthode .allowGlobals() :

    angular.module('todoApp')
      .config($controllerProvider){
        $controllerProvider.allowGlobals();
      }

    // work again
    function PersonCtrl($scope){
      ...
    }

Getter / Setter

Je vous avais déjà parlé de la directive ngModelOptions. Une autre option est dorénavant disponible pour celle-ci : celle d’activer un mode getter/setter pour votre modèle.

    <input ng-model="todo.name" ng-model-options="{ getterSetter: true }" />

Votre controller devra alors posséder un attribut du scope todo.name qui sera une fonction. Si celle-ci est appelée sans paramètres, elle renverra la valeur de l’attribut, sinon elle modifiera éventuellement la valeur de l’attribut avec la valeur du paramètre, de la façon dont vous le déciderez. Un getter/setter qui fonctionnerait sans modification ressemblerait à celui-ci :

    var _name = 'internal name'
    $scope.todo {
      name: function(newName){
        _name = angular.isDefined(newName) ? newName : _name;
        return _name;
      }
    }

L’intérêt principal réside dans le fait que l’on peut alors avoir une représentation interne différente de la représentation envoyée à l’utilisateur. Cela peut remplacer ce qui aurait été précédemment fait avec un watcher, voir cet exemple avec $location.

Vous pouvez alors accéder à votre attribut par son getter avec todo.name() ou changer sa valeur avec todo.name(‘new name’).

multiElement directives

Si vous utilisez des directives multi élements, par exemple :

    <div></div>
    <div>... some content</div>
    <div></div>

Vous devez maintenant déclarer la directive avec un attribut multiElement :

    angular.module('directives')
      .directive('poney', function() {
        return {
          multiElement: true,
          ...
        };
      });

Directives as Element or Attribute by default

Autre nouveauté dans les directives, elles sont maintenant par défaut à la fois un élément et un attribut (restrict: ‘EA’). Jusqu’ici, si rien n’était précisé, la directive cherchait seulement les attributs (restrict: ‘A’).

ngRepeat alias

Vous savez qu’il est possible de filtrer la collection que vous allez afficher dans un ngRepeat, par exemple :

    <div ng-repeat="poney in poneys | filter:search">{{ poney }}</div>

C’est très pratique, mais si l’on veut utiliser le nombre de résultats affichés, on est obligé de réappliquer le même filtre à la même collection :

    <div ng-repeat="poney in poneys | filter:search">{{ poney }}</div>
    <div ng-if="(poneys | filter:search).length === 0">No results.</div>

La version 1.3 introduit un alias as, qui permet justement de stocker le résultat du filtre dans le ngRepeat et de le réutiliser plus tard.

    <div ng-repeat="poney in poneys | filter:search as poneysFiltered">{{ poney }}</div>
    <div ng-if="poneysFiltered.length === 0">No results.</div>

New promise syntax

Il est maintenant possible de construire une promise directement avec le constructeur $q.

Précédemment, il était nécessaire d’utiliser cette syntaxe, dont je n’ai jamais été très fan :

    // creation
    var d = $q.defer();
    if(success){
      d.resolve('foo');
    }
    var promise = d.promise;

    // use
    promise.then(function(result) {});

Il maintenant possible de faire plus élégant, et beaucoup plus proche de la syntaxe adoptée par ES6 :

    // creation
    var promise = $q(function(resolve) {
      if(success){
        resolve('foo');
      }
    });

    // use
    promise.then(function(result) {});

ngTouched and ngSubmitted

Jusqu’ici les champs d’un formulaire avaient les propriétés (et les règles CSS associées) :

$pristine si le champ était vierge
$dirty si l’utilisateur avait changé le modèle
$invalid si l’une des règles de validation n’était pas respectée
$valid sinon

Deux nouveaux états sont maintenant disponibles, avec les classes CSS associées :

$untouched si le champ a le focus
$touched si le champ a perdu le focus (même sans avoir changé le modèle)

Concrètement, cela permet d’afficher les erreurs de validation dès qu’un utilisateur sort du champ, même sans l’avoir modifié, là ou $dirty demande d’avoir au moins une modification pour s’appliquer. Par exemple :

    <form name="userForm">
      <input name="email" type="email" ng-model="email" required>
      <div ng-if="userForm.email.$touched && userForm.email.$invalid">Required email</div>
    </form>

Nous saurons également maintenant si un formulaire a été soumis ou non avec l’attribut $submitted qui sera géré au niveau de celui-ci, et avec la classe ng-submitted qui apparaîtra une fois le formulaire soumis.

Vous pouvez tester l’exemple dans ce Plunker.

Déjà une vingtaine de betas sont sorties pour cette version 1.3.0, on se rapproche de la sortie finale. Mais il reste probablement de quoi faire un dernier article avant celle-ci ;).

Les nouveautés d’AngularJS 1.3 – Part 2

Nous poursuivons notre exploration des nouvelles features d’AngularJS 1.3! Si vous avez raté la première partie, c’est par ici.

Et bien sûr, vous pourrez retrouver tout ça dans notre livre dont la sortie se rapproche!

ngStrictDI

AngularJS offre un système d’injection de dépendances qui permet d’injecter les services voulus dans un composant :

    app.controller('MyCtrl', function($scope, $http){
      // $scope and $http are injected by AngularJS
    });

C’est très pratique, mais cela a un « léger » problème si vous minifiez votre application : $scope va devenir a, $http va devenir b, et là c’est le drame, l’injection ne fonctionne plus car Angular se base sur les noms.

Pour éviter cela, une autre syntaxe est disponible :

    app.controller('MyCtrl', ["$scope", "$http", function($scope, $http){
      // $scope and $http are injected by AngularJS
      // and now handles minification
    }]);

Et là, plus de problème. Il faut donc penser à utiliser cette syntaxe (ou s’appuyer sur un plugin dans votre build qui fasse tout ça automatiquement comme ng-min ou ng-annotate. Une autre syntaxe, basée sur l’attribut $inject est aussi proposée. Mais il arrive qu’un développeur oublie de l’utiliser à un seul endroit et l’application ne démarre pas, ce qui est bien sûr un peu pénible.

C’est là ou la version 1.3 apporte une nouveauté : il est désormais possible de démarrer Angular en mode strict-di (injection de dépendance stricte), et un beau message d’erreur pour indiquer que tel composant n’utilise pas la bonne syntaxe apparaît.

    <body ng-app="app" ng-strict-di>...</body>

    app.controller('MyBadCtrl', function($scope, $http){
      // not allowed with strict DI
    });
    // --&gt; 'MyBadCtrl is not using explicit annotation and cannot be invoked in strict mode'

ngMessages

Un nouveau module est également apparu, c’est le module ngMessages.

Comme je l’expliquais dans l’article précédent, il est possible en AngularJS d’afficher des messages d’erreur sur vos formulaires en fonction de différents critères : si le champ est vierge ou si l’utilisateur l’a touché, si le champ est obligatoire, si le champ viole une contrainte particulière… Bref vous pouvez afficher le message que vous voulez!

Le reproche fait à la version actuelle est que la condition booléenne à écrire pour afficher le message est souvent verbeuse du type :

    <span ng-if="eventForm.eventName.$dirty && eventForm.eventName.$error.required">The event name is required</span>
    <span ng-if="eventForm.eventName.$dirty && eventForm.eventName.$error.minlength">The event name is not long enough</span>
    <span ng-if="eventForm.eventName.$dirty && eventForm.eventName.$error.maxlength">The event name is too long</span>
    <span ng-if="eventForm.eventName.$dirty && eventForm.eventName.$error.pattern">The event name must be in lowercase</span>

Ce n’est pas insurmontable mais on a souvent plusieurs messages par champ, et plusieurs champs par formulaire : on se retrouve avec un formulaire HTML bien rempli ! On peut aussi vouloir n’afficher qu’un seul message à la fois, par ordre de priorité.

C’est ici qu’entre en jeu le nouveau module, l’exemple précédent pourra s’écrire :

    <div ng-if="eventForm.eventName.$dirty" ng-messages="eventForm.eventName.$error">
      <div ng-message="required">The event name is required</div>
      <div ng-message="minlength">The event name is too short</div>
      <div ng-message="maxlength">The event name is too long</div>
      <div ng-message="pattern">The event name should be in lowercase</div>
    </div>

C’est un peu plus lisible et cela gère pour nous le fait de n’afficher qu’un seul message et de les afficher par ordre de priorité. Si vous voulez afficher plusieurs messages à la fois, c’est facile, vous ajoutez la directive ng-messages-multiple.

Il est possible d’écrire tout ça différemment, avec un attribut multiple :

    <ng-messages for="eventForm.eventName.$dirty" multiple>
      <ng-message when="required">...</ng-message>
      <ng-message when="minlength">...</ng-message>
      <ng-message when="maxlength">...</ng-message>
      <ng-message when="pattern">...</ng-message>
    </ng-messages>

Le dernier avantage à utiliser ngMessages réside dans la possibilité d’externaliser les messages d’erreur dans des templates, offrant ainsi la possibilité de les réutiliser par ailleurs.

    <!-- error-messages.html -->
    <ng-message when="required">...</ng-message>
    <ng-message when="minlength">...</ng-message>
    <ng-message when="maxlength">...</ng-message>
    <ng-message when="pattern">...</ng-message>

Puis dans votre formulaire :

    <ng-messages for="eventForm.eventName.$dirty" ng-include-messages="error-messages.html"/>

Et si jamais les messages d’erreur sont trop génériques, vous pouvez les surcharger directement dans votre formulaire :

    <ng-messages for="eventForm.eventName.$dirty" ng-include-messages="error-messages.html">
      <ng-message when="required">An overloaded message</ng-message>
    </ng-messages>

A noter que cela fonctionne également avec vos directives de validation custom!

Là encore, amusez-vous avec le Plunker associé.

Watchgroup

Le $scope est enrichi d’une nouvelle méthode pour observer un ensemble de valeurs : watchGroup. Elle fonctionne sensiblement comme la méthode watch déjà existante, à la nuance près qu’au lieu d’observer une seule valeur, elle en observe une collection :

    $scope.watchGroup([user1, user2, user3], function(newUsers, oldUsers) {
      // the listener is called any time one of the user is updated
      // with newUsers representing the new values and oldUsers the old ones.
    );

One time binding

Il est maintenant possible de demander à Angular de cesser d’observer une expression une fois celle-ci évaluée. Il suffit pour cela de la précéder de :: dans vos templates. Ainsi tout changement ultérieur ne sera pas répercuté à l’affichage.

    $scope.name = 'Cédric';

    <!-- updating the input won't affect the displayed div -->
    <input type="text" ng-model="name">
    <div>Bind once {{ ::name }}</div>

Cela fonctionne aussi avec les collections, par exemple lorsqu’elles sont utilisées dans un ng-repeat :

    $scope.tasks = ["laundry", "running", "shopping"];
    $scope.remove = function(){ $scope.tasks.pop(); };

    <!-- removing tasks will not affect the displayed list -->
    <button ng-click="remove()">remove</button>
    <div ng-repeat="task in ::tasks">
      {{ task }}
    </div>

Bien sûr, c’est plus sympa à essayer dans le Plunker associé.

Il reste encore quelques fonctionnalités sympathiques à couvrir dans cette version 1.3 : stay tuned !

Les nouveautés d’AngularJS 1.3

La team Angular prépare avec amour sa nouvelle version. Ayant abandonné son précédent système de versionning pour adopter le semantic versionning, la 1.3 sera donc la nouvelle version stable (précédemment 1.0.x, puis 1.2.x, la 1.1.x étant la branche de développement, à la Unix Kernel).

Mais que contient cette version fraichement émoulue? Outre les nombreux « fixes », on note l’abandon définitif du support d’IE8 : cette fois vous êtes seuls au monde si votre application doit tourner sur ce bon vieux IE. Pour le reste, nous avons passé au crible pour vous les différents commits (plus de 400!) qu’elle contient !

Disclaimer
Nous sommes dans la phase finale de rédaction d’un livre sur AngularJS en français, nous vous en dirons bientôt plus! Et en attendant, si vous voulez en savoir plus, allez voir notre formation, qui est d’ores et déjà à jour!

ngModelOptions

Jusqu’ici la directive ngModel lançait une mise à jour du model à chaque action utilisateur (donc à chaque frappe clavier par exemple, les validations étaient exécutées, et une boucle $digest se déroulait). Cela fonctionne très bien, mais peut être pénalisant si vous avez une page avec beaucoup de bindings surveillés et à mettre à jour. C’est ici qu’intervient cette nouvelle directive : ngModelOptions.

Il devient ainsi possible de changer ce comportement par défaut et de déclencher ces différents événements selon votre envie, en passant un objet représentant les options à cette directive. La pull-request était ouverte depuis plus d’un an, comme quoi il ne faut jamais désespéré quand on contribue à un projet populaire! Tous les inputs portant un ngModel vont alors chercher ces options soit sur l’élément actuel ou sur ses ancêtres.

Ces options peuvent contenir un champ updateOn qui définit sur quel événement utilisateur la mise à jour du modèle doit être effectuée. Vous pouvez par exemple ne la déclencher que sur la perte de focus du champ :

<input name="guests" type="number" ng-required="true" ng-model="event.guests" max="10" ng-model-options="{ updateOn: 'blur' }">

Il est possible de passer plusieurs événements par exemple blur pour la perte de focus et paste pour déclencher la mise à jour si l’utilisateur colle une valeur dans le champs.

<input name="guests" type="number" ng-required="true" ng-model="event.guests" max="10" ng-model-options="{ updateOn: 'blur paste' }">

Pour conserver le comportement normal, utilisez l’événement default : utile si vous voulez simplement ajouter de nouveaux événements.

Les options peuvent également contenir un champ debounce qui spécifie un temps d’attente depuis le dernier événement avant de lancer la mise à jour. Par exemple, seulement une seconde après la dernière action utilisateur :

<input name="guests" type="number" ng-required="true" ng-model="event.guests" max="10" ng-model-options="{ debounce: 1000 }">

La valeur peut être un nombre si le même temps doit être attendu pour tous les événements, ou un objet avec comme attribut chacun des événements pour lequel vous voulez spécifier un debounce et la valeur de celui-ci.

<input name="guests" type="number" ng-required="true" ng-model="event.guests" max="10" ng-model-options="{ debounce: { default: 0, paste: 500 }}">

On peut donc également définir cette option pour toute une page ou tout un formulaire, puisque chaque ngModel recherchera les options également sur ses ancêtres :

<form name="eventForm" ng-model-options="{ debounce: 1000 }">
  <input name="week" type="date" ng-required="true" ng-model="event.weekNumber">
  <input name="guests" type="number" ng-required="true" ng-model="event.guests" max="10">
</form>

Cette nouvelle option est assez pratique, notamment pour un cas très précis qui arrive parfois. Imaginez que vous ayez un formulaire d’inscription et que le champ login vérifie si la valeur entrée par l’utilisateur est disponible côté serveur : on voit ici l’intérêt du debounce pour attendre que l’utilisateur termine son entrée de valeur, ou d’attendre la perte de focus pour faire la vérification, plutôt que de lancer une requête HTTP à chaque frappe clavier!

<!-- poney-unique-name is a custom validator, implemented as a directive, checking with the server if the value is not already taken by another poney -->
<input name="name" type="text" ng-required="true" poney-unique-name ng-model="poney.name" ng-model-options="{updateOn: 'blur'}">

Un problème peut cependant apparaître avec cette approche. Imaginez un bouton ‘Clear’ sur votre formulaire qui vide les champs. Si jamais l’utilisateur remplit un champ avec un debounce, puis clique sur ‘Clear’, les champs vont se vider, puis le debounce s’éxecuter et remplir à nouveau le champ! Il faut donc penser à supprimer tous les debounces en attente dans le code de la méthode clear() appelée par le clic sur le bouton, en utilisant une nouvelle méthode exposée par le controller du champ, appelée $rollbackViewValue.

Vous pouvez jouer avec cette nouvelle fonctionnalité, et ses limites, avec ce Plunker.

Input date

Jusqu’à maintenant, les formidables capacités d’AngularJS pour les formulaires permettaient de gérer la validation des types text, number, email, url, en s’appuyant sur les types d’input HTML5 dans les navigateurs récents et en la simulant à l’aide d’un polyfill dans les autres. Plus besoin donc d’écrire vos propres expressions régulières, le travail est fait pour vous, et le framework se charge d’ajouter dynamiquement des classes CSS sur l’élément (par exemple ng-invalid-email) en cas de violation, ainsi que de maintenir une représentation de votre champ (et du formulaire plus globalement) en JS pour permettre de faire :

<form name="userForm">
  <input name="email" type="email" ng-required="true" ng-model="user.email">
  <span ng-show="userForm.email.$error.email">Email is incorrect</span>
</form>

La variable userForm représente le controller du formulaire (le nom vient du champ name du formulaire), son attribut email représente lui le controller du champ email.

Ainsi le message d’alerte indiquant que le champ n’est pas bien rempli ne s’affiche que si l’email est incorrect. Rien d’autre à faire pour nous, Angular s’occupe de tout.

Vous pouvez vous référer à cet article précédent pour plus d’informations sur le sujet.

La version 1.3 apporte maintenant la gestion des type ‘date’, en utilisant là encore le support HTML5 si disponible (et vous aurez alors le plus ou moins beau date-picker de votre navigateur), ou un champ texte sinon, dans lequel il faudra entrer une date au format ISO-8601, par exemple ‘yyyy-MM-dd’. Le modèle lié doit être une Date JS.

<form name="userForm">
  <input name="birthDate" type="date" ng-required="true" ng-model="user.birthDate" min="1900-01-01" max="2014-01-01">
  <span ng-show="userForm.birthDate.$error.date">Date is incorrect</span>
  <span ng-show="userForm.birthDate.$error.min">Date should be after 1900</span>
  <span ng-show="userForm.birthDate.$error.max">You should be born before 2014</span>
</form>

Si il y a les dates, il y a également les heures.
Si ce type n’est pas supporté par le navigateur, un champ texte sera utilisé et le format ISO sera HH:mm :

<form name="eventForm">
  <input name="startTime" type="time" ng-required="true" ng-model="event.startTime" min="06:00" max="18:00">
  <span ng-show="eventForm.startTime.$error.time">Time is incorrect</span>
  <span ng-show="eventForm.startTime.$error.min">Event should be after 6AM</span>
  <span ng-show="eventForm.startTime.$error.max">Event should be before 6PM</span>
</form>

Le modèle lié est une Date JS avec la date du 1 Janvier 1900 et l’heure saisie.

La version 1.3 supporte également les champs ‘dateTimeLocal’, qui sont donc une date et une heure. Si ce type n’est pas supporté par le navigateur, un champ texte sera utilisé et le format ISO sera yyyy-MM-ddTHH:mm :

<form name="eventForm">
  <input name="startDate" type="dateTimeLocal" ng-required="true" ng-model="event.startDate" min="2014-06-01T00:00" max="2014-06-30T23:59">
  <span ng-show="eventForm.startDate.$error.dateTimeLocal">Date is incorrect</span>
  <span ng-show="eventForm.startDate.$error.min">Event should be after May</span>
  <span ng-show="eventForm.startDate.$error.max">Event should be before July</span>
</form>

Enfin, il est possible d’utiliser les champs ‘month’ ou ‘week’, stocké également dans un modèle de type Date,
qui permettent évidemment de choisir un mois ou une semaine.
Si ce type n’est pas supporté par le navigateur, un champ texte sera utilisé et le format ISO à utiliser sera yyyy-## ou yyyy-W## pour les semaines :

<form name="eventForm">
  <input name="week" type="week" ng-required="true" ng-model="event.weekNumber" min="2014-W01" max="2014-W52">
  <span ng-show="eventForm.week.$error.week">Week is incorrect</span>
  <span ng-show="eventForm.week.$error.min">Event should be after 2013</span>
  <span ng-show="eventForm.week.$error.max">Event should be before 2015</span>
</form>

Ces deux fonctionnalités ne sont qu’une partie de celles offertes par la nouvelle version : on en garde quelques unes pour un prochain post. Stay tuned!

Angular, Express et Socket.io

Vous savez, si vous lisez le blog de Ninja Squad régulièrement, que nous donnons des cours dans différents établissements, de la fac aux écoles d’ingé, en passant par l’IUT. Avec cette nouvelle année scolaire, nous voici repartis!

Je donne depuis quelques années un cours sur les web services à l’INSA, à l’invitation du Docteur Ponge : c’est l’occasion de parler SOAP (beurk), REST (yay!) et Websocket (re-yay!).

Cette année, je veux faire une démo des websockets pour que cela soit un peu moins mystérieux. Faire un peu de code live est toujours un exercice périlleux, mais c’est un bon moyen pour échanger avec les étudiants et leur présenter un certain nombre de technologies, qu’ils connaissent parfois de nom ou pas du tout.

Les websockets sont l’occasion idéale de faire une application web basique, mais permettant d’introduire quelques concepts du Web parfois mal connus (HTML, CSS, Javascript) et un peu de code plus innovant (Node.js, Express, Socket.io, AngularJS, Bootstrap), de discuter sur l’histoire du Web, le fonctionnement des navigateurs (Chrome, Firefox, V8…) et quelques outils pratiques (npm, Git). Bref, d’apporter un peu de culture générale de notre métier, chose que j’appréciais beaucoup lors des interventions de professionnels lorsque j’étais étudiant.

Après cette remise en contexte, passons aux choses sérieuses!

Il me fallait donc un exemple d’application, qui me prenne une trentaine de minutes à réaliser au maximum. J’ai choisi de faire simple : une application de vote qui permet de choisir votre framework Javascript préféré, et de voir les résultats en temps réel. Nous allons voir les différentes étapes pour y parvenir.

Vous trouverez le nécessaire pour utiliser l’application sur le repo Github de Ninja Squad.

Express/Angular

La première branche Git, nommée `express` met en place une application Node.js/Express minimale.

    var express = require('express')
      , app = express()
      , server = require('http').createServer(app);
    
    app.use(express.static(__dirname + '/'));
    
    server.listen(9003);

L’application Node.js va servir les ressources statiques sur le port 9003. On peut alors ajouter le fichier HTML de notre application, qui contient basiquement :

    <div class="row">
      <div class="col-xs-4 vote"> {{ vote.choice }} </div>
      <div class="col-xs-4 vote"> {{ vote.votes }} </div> 
      <div class="btn btn-primary col-xs-4">+1</div>
    </div>

Pour chaque vote de la collection `votes`, le choix (VanillaJS, AngularJS, BackboneJS ou EmberJS), le nombre de vote pour ce choix et un bouton pour ajouter un vote seront affichés. Cela est réalisé en utilisant la directive `ng-repeat` d’Angular.

Le bouton de vote comporte l’attribut `ng-click` qui permet de lui lier une fonction à exécuter. Cette fonction `voteFor` est définie dans le controller :

	function VoteCtrl($scope){
      $scope.votes = [ { choice: 1, label: 'VanillaJS', votes: 0 }, { choice: 2, label: 'AngularJS', votes: 0 }, { choice: 3, label: 'BackboneJS', votes: 0 }, { choice: 4, label: 'EmberJS', votes: 0 }];

      $scope.voteFor = function(choice){ $scope.votes[choice-1].votes++; }
    }

Le controller Angular initialise les votes et définit la fonction de vote, qui ajoute simplement 1 aux votes du choix cliqué.

Etape 1 terminée! Passons maintenant à l’intégration de Socket.io.

Socket.io

Socket.io est l’une des librairies les plus utilisées pour les websockets dans Node (même si elle est maintenant concurrencée par d’autres comme SockJS). Elle a l’avantage de gérer le fallback si les websockets ne sont pas disponibles, et d’être très simple à utiliser à la fois côté client et côté serveur.

La branche `websocket` contient le code correspondant. Outre l’installation de socket.io (`npm install` is your best friend), il nous faut modifier un peu le serveur :

    io.sockets.on('connection', function (socket) {
      socket.emit('votes', { votes: votes });
      socket.on('vote', function(msg){
      	votes[msg.vote-1].votes++;
      	io.sockets.emit('votes', { votes: votes });
      })
    });

L’implémentation est naïve mais suffit à la démonstration : à la connexion d’un nouveau client (`socket.on(‘connection’, …)`), on envoie les votes dans l’état du moment (`socket.emit`). Puis, lorsque l’on recevra un vote (`socket.on(‘vote’, …)`), on incrémente les votes du choix correspondant et on informe tous les participants avec les nouvelles valeurs (`io.sockets.emit`).

Reste à mettre à jour le client pour communiquer avec les sockets :

    var socket = io.connect('http://localhost:9003');

    $scope.voteFor = function(choice){
      socket.emit('vote', {vote : choice })
    }

    socket.on('votes', function(msg){
      $scope.votes = msg.votes;
      $scope.$apply();
    });

On commence par se connecter aux websockets (`io.connect`). La fonction `voteFor` est modifiée pour maintenant envoyer un évenement de vote au serveur (`socket.emit`). Enfin, à chaque fois que les votes sont reçus, les votes du `$scope` Angular sont mis à jour. Petite subtilité : comme cette mise à jour intervient en dehors de la boucle d’exécution d’Angular, il nous faut appeler la fonction `$apply()` pour que le framework prenne les nouvelles valeurs en compte et rafraîchisse les vues.

Nous sommes prêts : si vous ouvrez cette application dans deux onglets, vous pourrez la voir se mettre à jour en temps réel!

A noter que le brillant Brian Ford de l’équipe Angular propose un service Angular pour Socket.io, afin de simplifier son utilisation et notamment les appels à `$apply()`.

Vous pouvez voir une démo en ligne, déployé sur Heroku (le code nécessaire pour cette partie est également sur le repo Github).

Espérons que ce petit essai vous plaise et plaise à nos étudiants!

Forms in AngularJS

Si vous lisez ceci, vous savez probablement créer un formulaire en HTML. Toute application web en contient son lot. Un formulaire contient un ensemble de champs, chaque champ étant une manière pour l’utilisateur de saisir des informations. Ces champs groupés ensemble dans un formulaire ont un sens, que ce soit une page d’enregistrement pour vos utilisateurs, ou une formulaire de login.

Les formulaires en Angular étendent les formulaires HTML en leur ajoutant un certain nombre d’états et en donnant aux développeurs de nouvelles façon d’agir.

Les formulaires, comme beaucoup de composants en Angular, sont des directives. Chaque fois que vous utilisez un formulaire en Angular, la directive ‘form’ va instancier un controller nommé ‘FormController’. Il est possible d’accéder au controller dans votre code en utilisant comme nom l’attribut ‘name’ de votre formulaire.

Ce controller expose un ensemble de méthode et de propriétés :
– ‘$pristine’ : permet de savoir si l’utilisateur a déjà touché au formulaire. On pourrait traduire en français par vierge. Ce booléen sera donc vrai tant qu’il n’y aura pas eu d’interaction, puis faux ensuite.
– ‘$dirty’ : à l’inverse de ‘$pristine’, ‘$dirty’ sera vrai si l’utilisateur a commencé à interagir avec le formulaire. Pour résumer : $dirty == !$pristine.
– ‘$valid’ : sera vrai si l’ensemble des champs (et éventuellement des formulaires imbriqués) sont valides.
– ‘$invalid’ : sera vrai si au moins un champ (ou formulaire imbriqué) est invalide.
– ‘$error’ : représente un dictionnaire des erreurs, avec comme clé le nom de l’erreur, par exemple ‘required’, et comme valeur la liste des champs avec cette erreur, par exemple login et password.

Ces états sont disponibles sur le formulaire global mais également sur chaque champ du formulaire. Si votre formulaire se nomme ‘loginForm’, et contient un champ ‘password’ alors vous pouvez voir si ce champ est valide grâce au code suivant :

    loginForm.password.$valid

Vous vous voyez déjà ajouter du code JS pour surveiller ces états et changer le CSS en cas d’erreur. Pas besoin! Angular ajoute ces états comme classe CSS directement sur chaque champ et le formulaire. Ainsi lorsque le formulaire encore vierge s’affiche, il contient déjà la classe CSS ‘ng-pristine’ :

    <form name='loginForm' class='ng-pristine'>
      <input name='login' ng-model='user.login' class='ng-pristine'/>
      <input name='password' ng-model='user.password' class='ng-pristine'/>
    </form>

Dès la saisie du premier caractère dans le champ ‘input’, le formulaire devient dirty :

    <form name='loginForm' class='ng-dirty'>
      <input name='login' ng-model='user.login' class='ng-dirty'/>
      <input name='password' ng-model='user.password' class='ng-pristine'/>
    </form>

A noter que le formulaire, ainsi que l’input, reste dirty une fois modifié, même si la modification est annulée.

Si l’un des champs présente une contrainte (comme par exemple ‘required’ ou ‘url’), alors le formulaire sera invalide tant que cette contrainte ne sera pas satisfaite. Le champ comme le formulaire aura donc la classe ‘ng-invalid’ ainsi que le champ en question. Une fois la condition satisfaite, la classe ‘ng-valid’ remplace la classe ‘ng-invalid’.

Vous pouvez donc très facilement customiser votre CSS pour ajouter un style selon l’état de votre formulaire. Cela peut être un bord rouge en cas de champ invalide :

    input.ng-dirty.ng-invalid {
      border: 1px solid red;
    }

Ou encore afficher un message d’erreur dans votre HTML :

    <form name='loginForm'>
      <input name='login' type='email' ng-model='user.login'/>
      <span ng-show='loginForm.login.$invalid'>Your login should be a valid email</span>
      <input name='password' ng-model='user.password'/>
    </form>

Ajouter un type ’email’ sur un champ place une contrainte sur celui-ci, obligeant l’utilisateur à entrer un login sous la forme d’une adresse email valide. Dès que l’utilisateur commencera sa saisie de login, le champ deviendra invalide, rendant l’expression ‘loginForm.login.$invalid’ vraie. La directive ‘ng-show’ activera alors l’affichage du message d’avertissement. Dès que le login saisi sera un email valide, l’expression deviendra fausse et l’avertissement sera caché. Plutôt pas mal pour une ligne de HTML non?

Vous pouvez bien sûr cumuler les conditions d’affichage de l’avertissement, ou faire un avertissement par type d’erreur :

    <form name='loginForm'>
      <input name='login' required type='email' ng-model='user.login'/>
      <span ng-show='loginForm.login.$dirty && loginForm.login.$error.required'>A login is required</span>
      <span ng-show='loginForm.login.$error.email'>Your login should be a valid email</span>
      <input name='password' ng-model='user.password'/>
    </form>

Ainsi si l’utilisateur, après avoir entré un login (rendant ainsi le champ « dirty »), l’efface, un message d’avertissement apparaîtra pour indiquer que le login est nécessaire. Les combinaisons ne sont limitées que par votre imagination!

Plusieurs méthodes sont disponibles sur le controller :
– ‘addControl()’, ‘removeControl()’ permettent d’ajouter ou de supprimer des champs du formulaire. Par défaut tous les inputs « classiques » (input, select, textarea) que vous utilisez avec un ‘ng-model’ sont ajoutés au formulaire pour vous. Ils ont tous un controller nommé ngModelController, qui gère justement les états ($pristine, $valid, etc…), la validation, l’ajout au formulaire pour vous ainsi que le binding des vues au modèle. Les méthodes ‘addControl()’ et ‘removeControl()’ peuvent être intéressantes si vous voulez ajouter un autre type de champ à votre formulaire.
– ‘setDirty()’, ‘setPristine()’ vont respectivement mettre le formulaire dans un état ‘dirty’ou ‘pristine’ avec les classes CSS associées positionnées. A noter que les méthodes se propagent vers les formulaires parents si ils existent. En revanche, seule la méthode ‘setPristine()’ s’applique sur chaque champ du formulaire. Vous pouvez donc faire un ‘reset’ du formulaire et de ses champs grâce à ‘$setPristine()’.
– ‘setValidity(errorType, isValid, control)’ va, comme son nom l’indique, passer un champ de votre formulaire comme $valid ou $invalid pour le type d’erreur précisé, affectant par le même coup la validité du formulaire ainsi que celles des éventuels formulaires parents.

La dernière chose à retenir sur les formulaires concerne la soumission. Angular étant conçu pour les « single page applications », on cherche à éviter les rechargements non nécessaires. Ainsi l’action par défaut du formulaire sera désactivée, à moins que vous le vouliez explicitement en précisant une ‘action’ dans la balise ‘form’. Angular cherchera plutôt à vous faire gérer vous même la soumission du formulaire, soit en utilisant la directive ‘ngSubmit’ dans la balise ‘form’ soit en utilisant la directive ‘ngClick’ sur un ‘input’ de type ‘submit’ (mais pas les deux à la fois, sinon vous avez une double soumission!).

Vous avez maintenant toutes les clés pour faire une bonne validation côté client de vos formulaires en Angular. Mais que cela ne vous empêche de vérifier côté serveur! 😉

Envie d’en savoir plus? Je donne une formation sur AngularJs!

Angular filter – Part 1

L’une des fonctionalités les plus appréciables et méconnues d’Angular réside dans les filtres disponibles. Dans toutes applications web, il est courant de devoir filtrer ou réordonner une collection pour l’afficher. Les filtres Angular sont applicables à la fois en HTML ou en Javascript, à un seul élément ou un tableau. En HTML, la syntaxe se rapproche du style Unix, où l’on peut chaîner les commandes à l’aide du pipe. Par exemple, on peut appliquer un filtre nommé ‘uppercase’ sur une expression de la façon suivante :

   {{ expression | uppercase }}

On peut également chaîner plusieurs appels de filtre :

    {{ expression | uppercase | trim }}

Certains filtres prennent des paramètres : c’est le cas du filtre ‘number’, qui nécessite de préciser le nombre de chiffres suivant la virgule. Pour passer les paramètres au filtre, il suffir de procéder comme suit :

    {{ expression | number:2 | currency:'$'}}

Il est également possible d’invoquer les filtres depuis le code Javascript :

    $filter('uppercase')

Angular va alors retrouver la fonction de filtre correspondant à la chaîne de caractères passée en paramètre.

Angular propose par défaut certains filtres communs :

– number : permet de préciser le nombre de chiffres après la virgule à afficher (arrondi au plus proche).

    {{ 87.67 | number:1 }} // 87.7 
    {{ 87.67 | number:3 }} // 87.670

– currency : permet de préciser la monnaie.

    {{ 87.67 | currency:'$' }} // $87.67

– date : permet de formatter l’affichage des dates, en précisant un pattern. On retrouve l’écriture classique de pattern :

    {{ today | date:'yyyy-MM-dd' }} // 2013-06-25

Un certain nombre de patterns sont disponibles (avec un rendu différent selon la locale) :

    {{ today | date:'longDate' }} // June 25, 2013

– lowercase/uppercase : de façon assez évidente, ces filtres vont convertir l’expression en majuscules ou minuscules.

    {{ "Cedric" | uppercase }} // CEDRIC   
    {{ "Cedric" | lowercase }} // cedric

– json : moins connu, ce filtre permet d’afficher l’objet au format JSON. Il est également moins utile, car, par défaut, afficher un object avec la notation ‘{{ }}’ convertit l’objet en JSON.

    {{ person | json }} // { name: 'Cedric', company: 'Ninja Squad'} 
    {{ person }} // { name: 'Cedric', company: 'Ninja Squad'} 

– limitTo : ce filtre s’applique quant à lui à un tableau, en créant un nouveau tableau ne contenant que le nombre d’éléments passés en paramètre. Selon le signe de l’argument, les éléments sont retenus depuis le début ou la fin du tableau.

    {{ ['a','b','c'] | limitTo:2 }} // ['a','b'] 
    {{ ['a','b','c'] | limitTo:-2 }} // ['b','c'] 

– orderBy : là encore un filtre s’appliquant à un tableau. Celui-ci va trier le tableau selon le prédicat passé en paramètre. Le prédicat peut être une chaîne de caractères représentant une propriété des objets à trier ou une fonction. Le prédicat sera appliqué sur chaque élément du tableau pour donner un résultat, puis le tableau de ces résultats sera trié selon les opérateur ‘. Une propriété peut être précédée du signe ‘-‘ pour indiquer que le tri doit être inversé. A la place d’un simple prédicat, il est possible de passer un tableau de propriétés ou de fonctions (chaque propriété ou fonction supplémentaire servant à affiner le tri primaire). Un second paramètre, booléen, permet quant à lui d’indiquer si le tri doit être inversé.

    var jb = {name: 'JB', gender: 'male'};    
    var cyril = {name: 'Cyril', gender: 'male'};     
    var agnes = {name: 'Agnes', gender: 'female'};     
    var cedric = {name: 'cedric', gender: 'male'};     
    $scope.ninjas = [jb, cyril, agnes, cedric];  
    
    // order by the property 'gender' 
    {{ ninjas | orderBy:'gender'}} // Agnes,JB,Cyril,Cédric 

    // order by a function (lowercase last) 
    $scope.lowercaseLast = function(elem){ 
      return elem.name === elem.name.toLowerCase() 
    }; 
    {{ ninjas | orderBy:lowercaseLast }} // Agnes,JB,Cyril,cedric 

    // order by an array of properties or functions 
    {{ ninjas | orderBy:['-gender','name'] }} // cedric,Cyril,JB,Agnes 

Dans le prochain billet, nous verrons comment créer nos propres filtres.

Getting started with lambdas – Part 2

Après une première partie dédiée à comprendre les interfaces fonctionnelles, nous allons entrer dans le vif du sujet avec cet article dont le but est de vous présenter les différentes façons d’écrire une lambda.

Supposons que nous ayons une interface Concatenator (histoire d’avoir un nom qui fait peur comme on sait faire en Java), qui prend un int et un double pour les concaténer :

interface Concatenator {
    String concat(int a, double b);                    
}

La première façon d’écrire une lambda implémentant cette interface sous forme de lambda sera :

Concatenator c = (int a, double b) -> { 
    String s = a + " " + b; 
    return s;
};

Voilà notre première lambda! Cette syntaxe est le fruit de nombreux mois de réflexion, après débat entre 3 propositions principales. On retrouve nos paramètres entre parenthèses avec leur type, suivi d’une flèche et d’un bloc de code entre accolades. Le bloc de code représente le corps de la fonction, c’est l’implémentation concrète de la méthode concat() de notre interface Concatenator. Ce corps de fonction est tout à fait classique avec un return en fin de bloc pour renvoyer le résultat.

Une autre façon, plus concise, d’écrire cette lambda est possible avec la syntaxe suivante :

Concatenator c = (int a, double b) -> return a + " " + b;

On note cette fois l’omision des accolades autour du bloc représentant la fonction. Cette syntaxe n’est bien sûr possible que si le corps de votre fonction tient sur une seule ligne, vous l’aurez compris.

Nous pouvons encore simplifier un peu l’écriture en utilisant un return implicite :

Concatenator c = (int a, double b) -> a + " " + b;

Le return a disparu ! En effet, par défaut, la valeur de l’expression à droite de la flèche est retournée par la lambda : les syntaxes avec et sans return sont donc parfaitement équivalentes.

Nous pouvons également nous reposer sur l’inférence de type et laisser le compilateur (lorsque c’est possible) déterminer les types de nos paramètres :

Concatenator c = (a, b) -> a + " " + b;

Vous voyez qu’une lambda peut être une expression très concise !

Si notre interface fonctionnelle avait une méthode avec un seul paramètre, par exemple :

interface UnaryOperator {
    int op(int a);
}   

Alors une lambda implémentant UnaryOperator pourrait être :

UnaryOperator op = (a) -> a * a;

Mais la lambda pourrait même ici se passer des parenthèses autour des paramètres :

UnaryOperator op = a -> a * a;

En revanche, une interface avec une méthode ne possédant pas de paramètres comme NumberSupplier :

interface NumberSupplier { 
   int get();
}

devra s’écrire :

NumberSupplier supplier = () -> 25;

Enfin, lorsque la lambda est un appel à une fonction de l’objet passé en paramètre, il est possible d’utiliser une syntaxe légérement différente. Ainsi, pour une interface fonctionnelle :

interface StringToIntFunction {                        
    int toInt(String s);
}

qui transforme une chaîne de caractères en entier, on peut écrire une lambda comme suit :

StringToIntFunction f = s -> s.length();

ou encore écrire :

StringToIntFunction f = String::length;

Ce :: est un nouvel opérateur Java : il agit comme un appel à la méthode length de l’argument de la méthode toInt. Un peu surprenant au début, mais on s’habitue vite. Cet opérateur peut également s’appliquer à un constructeur. Notre méthodetoIntpourrait donc également s’écrire :

StringToIntFunction f = Integer::new;

ce qui équivaut à

StringToIntFunction f = s -> new Integer(s);

L’opérateur peut aussi s’appliquer à une méthode statique, comme parseInt pour la classe Integer. La lambda :

StringToIntFunction f = s -> Integer.parseInt(s);

est donc identique à :

StringToIntFunction f = Integer::parseInt;

Enfin, il est aussi possible de faire référence à une méthode d’un autre objet. Si nous supposons l’existence d’une HashMapstringToIntMap dans notre code, nous pouvons écrire une lambda comme suit :

StringToIntFunction f = stringToIntMap::get;

qui signifie la même chose que :

StringToIntFunction f = s -> stringToIntMap.get(s);

Voilà, nous avons fait un inventaire exhaustif des façons d’écrire une lambda. La possibilité d’omettre les parenthèses, types, accolades et le mot clé return est appréciable et donne une syntaxe très peu verbeuse. L’ajout de l’opérateur :: introduit de nouvelles possibilités dans l’écriture comme vous avez pu le constater. Son utilisation demande un peu de pratique, mais cela viendra vite naturel !

La prochaine fois nous regarderons une petite subtilité sur la portée des variables utilisables dans une lambda. Teasing teasing…

Getting started with lambdas : Part 1

Si vous avez suivi l’actualité Java ces derniers temps, vous avez peut-être entendu que la sortie de la “developer preview” du JDK 8 a été repoussée. Bien sûr tout le monde s’en donne à coeur joie pour vanner Oracle, c’est de bonne guerre. Mais pour une fois, ce retard est peut-être une bonne chose : toute l’équipe du JDK travaille d’arrache-pied pour peaufiner la grande nouveauté de cette version : les lambdas. Nous avons suivi avec attention leur avancée ces derniers mois, testant des JDKs, pestant contre certains choix et en admirant d’autres. Si au début je trouvais un intérêt minimal aux lambdas par rapport à ce que proposent Scala et consort, j’avoue maintenant attendre la sortie de Java 8 pour profiter des nouveautés, et écrire du code un peu plus sympa dans un langage que j’apprécie.

Voilà assez parlé de ma vie.

Ce post démarre une série qui vise à présenter les lambdas, qui sont probablement la plus grande nouveauté dans le langage depuis les génériques. Plutôt que de vous montrer directement une lambda, et nous extasier devant leur utilisation, j’aimerais vous expliquer un peu ce qui se passe derrière.

Tout repose sur le concept d’interface fonctionnelle.

Une interface vous voyez ce que c’est : un ensemble de méthodes abstraites que l’on devra implémenter dans une classe. Jusque là ça va.

public interface MessageInterface {
  public String transform(String message);
  public void print(String message);
}

Mais cette définition change avec Java 8 : une interface peut maintenant avoir des méthodes concrètes et des méthodes statiques. Oui, oui, c’est nouveau. Le mot clé `default` est utilisé pour définir une méthode concrète dans une interface. Par exemple :

public interface MessageInterfaceWithDefault {
  public String transform(String message);
  public default void print(String message) {
    System.out.println(message);
  }
}

Ca fait bizarre, mais on s’y fait. Dans le cas de cette interface, la méthode `print()` a une implémentation par défaut. Il peut également y avoir plusieurs méthodes `default` bien sûr. Ainsi si vous voulez implémenter cette interface vous avez deux solutions :

public class MessageWithoutDefault implements MessageInterfaceWithDefault {
  public String transform(String message) {
    return message;
  }
  public void print(String message) {
   System.out.println(“message :” + message);
  }
}

Cette première solution implémente l’interface en donnant une implémentation à chaque méthode, la méthode `default` étant surchargée.

Une autre solution est possible, si l’implémentation par défaut de print vous convient, vous pouvez seulement fournir une implémentation à `transform()` :

public class MessageWithDefault implements MessageInterfaceWithDefault {
  public String transform(String message) {
    return message; // oui bon d'accord ça fait rien...
  }
}

Donc en résumé, une méthode `default` dans une interface vous fournit une implémentation par défaut, libre à vous de la redéfinir ou pas.

Qu’en est il de l’héritage multiple?

Si vous héritez de deux interfaces possédant une méthode `default` avec la même signature, le compilateur va regarder si l’une des interfaces hérite de l’autre. Si c’est le cas, l’interface la plus spécialisée est préférée. Sinon vous obtenez l’erreur de compilation suivante :

class training.lambda.Both inherits unrelated defaults for print() from types training.lambda.Interface1 and training.lambda.Interface2

Voilà pour les méthodes `default`.

Revenons-en aux interfaces fonctionnelles. Lorsque vous allez écrire une lambda, vous allez en fait implémenter une interface fonctionnelle. Une interface fonctionnelle est une interface qui ne possède qu’une seule méthode abstraite. Elle peut en revanche avoir plusieurs méthodes `default`. L’interface `MessageInterfaceWithDefault` est donc une interface fonctionnelle. Vous pourriez écrire :

MessageInterfaceWithDefault asALambda = message -> “message : “ + message;

Ce code est tout à fait valide. L’expression de droite est en fait l’implémentation de la méthode `transform()` de l’interface. Ainsi si vous exécutez le code suivant :

String result = asALambda.transform(“hello”); // result = “message : hello”

On est d’accord, il n’y a pas beaucoup d’intérêt à utiliser les lambdas ainsi. Cet exemple montre seulement ce que sont réellement les lambdas. Ainsi, vous ne pouvez pas utiliser les lambdas pour implémenter des interfaces qui possèdent plusieurs méthodes abstraites : le compilateur ne saurait pas quelle méthode vous essayez d’implémenter.

Le JDK contient déjà beaucoup d’interfaces fonctionnelles : `FileFilter`, `Runnable`, `Callable`, `ActionListener`, `Comparator`…

`FileFilter` a par exemple une seule méthode abstraite : `boolean accept(File pathname)`, qui prend donc un fichier en argument et renvoie un booléen. `FileFilter` est utilisé comme argument dans la methode `listFiles()` de `File`, pour renvoyer une liste de fichiers qui correspondent au filtre. Vous voulez récupérer les répertoires d’un dossier dir, en une ligne en utilisant les lambdas ?

File[] files = dir.listFiles(f -> f.isDirectory())

`f -> f.isDirectory()` est une lambda que le compilateur interprète comme l’implémentation d’un `FileFilter`. Rapide et efficace, je ne vous montre même pas le code que l’on doit écrire actuellement.

Dans votre code, vous pourrez faire vos propres interfaces fonctionnelles, qui seront joyeusement implémentées sous forme de lambda par vos collègues, parfois même dans des modules différents. Mais que se passe-t-il si quelqu’un ajoute une méthode abstraite à votre interface? Et bien les lambdas écrites par vos collègues vont poser problème à la compilation des modules qui utilise votre interface :

incompatible types: lambda.Interface1 is not a functional interface
multiple non-overriding abstract methods found in interface com.ninja_squad.training.lambda.Interface1

Pour prévenir ce problème, une annotation @FunctionalInterface a été ajoutée. Celle-ci indique au compilateur que l’interface en question ne doit avoir qu’une et une seule méthode abstraite. Si vous essayez d’ajouter une méthode abstraite à cette interface, vous obtenez l’erreur de compilation suivante :

Unexpected @FunctionalInterface annotation
training.lambda.Interface1 is not a functional interface
multiple non-overriding abstract methods found in interface training.lambda.Interface

Autant dire que ce garde-fou devra être systématiquement utilisé si vous créez des interfaces fonctionnelles afin de prévenir de potentiels (probables?) problèmes à l’avenir. Toute personne qui modifiera l’interface saura immédiatement à quoi s’en tenir!

La prochaine fois, on regardera comment écrire une lambda en détail (enfin!).

Scala on Coursera

La semaine dernière s’achevait le cours Functional Programming Principle in Scala de Coursera, animé par Martin Odersky en personne. Si vous ne connaissez pas Coursera, sachez que c’est l’une des startups californiennes qui se lance dans le domaine de l’éducation en ligne (l’une de leur dernière recrue est Pamela Fox, speakeuse à Mix-it 2012, et qui, alors qu’elle n’était pas encore employée, avait fait une keynote sur l’éducation). Sur le site, vous suivez des cours en vidéo, vous avez des exercices à faire et à rendre, vous recevez des notes et à la fin un diplome si vous avez été sages. Avec pas loin de 2 000 000 d’inscrits, le site a pris son envol, à juste titre il faut dire car la plateforme est très bien conçue. Martin Odersky, et sa société TypeSafe, frappe donc encore un grand coup marketing en lançant ce cours en ligne sur Coursera. Si ce nom vous est également inconnu, Martin est le créateur de Scala avec une équipe de l’EPFL (Ecole Polytechnique Fédérale de Lausanne) où il est chercheur. Il a également bossé sur le compilateur Java et notamment contribué aux génériques de Java5. On peut donc dire sans trop se tromper qu’il en connaît un rayon en matière de JVM et de langage.

 

Je ne l’avais jamais vu en revanche donner un cours, mais les videos sur Coursera qu’il a réalisé sont claires et compréhensibles à condition d’avoir un minimum d’anglais (et également de faire abstraction de l’accent allemand, qui est quand même plus compréhensible que l’accent français probablement). Le volume de cours n’est pas négligeable : s’étalant sur 7 semaines, il fallait quand même prévoir une à deux heures pour les vidéos et de quelques minutes à plusieurs heures pour les exercices. Mais cela n’a pas décourager les foules : le cours a compté 50000 participants (ou plutot inscrits, je ne sais pas si tous en sont venus à bout). Ce que j’en retiendrais :

 

Les plus :

– la plateforme Coursera : rien à dire, je trouve ça bien très bien fait. Les vidéos sont téléchargeables ou visionnables en ligne, avec une possibilité d’accélérer la lecture qui est très appréciable tout en restant compréhensible. Les exercices sont très clairs, vous téléchargez un projet tout prêt dans le cas de ce cours, une fois terminé, vous pouvez le soumettre en ligne de commande avec ‘submit monmail monpassword’. Des tests unitaires vont alors se lancer sur votre projet directement sur la plateforme et vous pouvez voir votre résultat sur la page de feedback. Il faut patienter quelques minutes mais rien de dramatique. Le système de note est intéressant, avec un petit côté gamification : on cherche à avoir le 10/10 même si une note inférieure permet de valider le cours. Seul point négatif à ce propos : l’absence de trace vraiment explicite pour comprendre quel test et quand votre programme a échoué : on se retrouve à mettre de vieux println dans le code pour essayer de cerner le problème.

– les leçons étaient très souvent intéressantes, mettant bien en avant la programmation fonctionnelle, avec des exemples bien choisis et des solutions élégantes. J’avais lu le livre d’Odersky “Programming in Scala” lors de sa sortie, et j’ai retrouvé des exemples du livre, mais je m’y suis bien moins ennuyé. Les exercices étaient loin d’être triviaux pour certains : celui sur les anagrammes que je pensais faire en quelques minutes m’a finalement pris les 2h de ma pause déjeuner… La progression était correcte, et j’ai eu l’impression d’apprendre des choses tout au long du cours : je pouvais difficilement en demander plus.

– au delà de Scala, difficile de ne pas aimer la programmation fonctionnelle. Et la pratiquer en Java est un vrai plaisir. Les collections scala sont géniales, vraiment. Dans la vie de tous les jours, je pallie comme beaucoup le manque grâce à l’excellente librairie Guava par exemple, et ce n’est pas sans déplaisir que je vois l’arrivée de Java8 (dont de nombreux concepts sont déjà dans Scala).

 

Le moins :

– Certains exercices étaient plus “mathématiques” que “programmatique”, j’avais l’impression de retourner en prépa plus que d’apprendre un langage. On sent ici le côté académique de Odersky et je me demande si cela n’a pas rebuté un certain nombre de participants. Cela se sent aussi sur le langage en lui même, avec son système de type. Ce n’est pas le genre de chose qui m’intéresse outre mesure (que le système de type soit complet ou pas…), et certaines vidéos à ce sujet étaient moins intéressantes à regarder.

– Je me suis limité à utiliser l’IDE conseillé, un Eclipse packagé pour Scala. Et j’ai trouvé l’expérience horrible (je travaille pourtant très souvent sous Eclipse), l’IDE plantait régulièrement, la complétion était longue, les erreurs souvent obscures et lancer les tests était lent. Autrement dit je ne sais pas comment les équipes qui travaillent sur des projets conséquents se débrouillent pour être productives. Il est fort possible qu’il y ait eu un problème avec ma machine, mais j’ai un macbook avec jdk7 assez standard : j’imagine donc que d’autres peuvent avoir ce problème. J’ai vu que beaucoup utilisait TextMate, et IntelliJ pour certains, ce qui est certainement mieux mais pas toujours possible dans une entreprise. Et globalement tout est lent, la compilation, les tests, l’execution… TypeSafe fournit un gros travail à ce sujet mais il y encore du chemin à parcourir.

– J’ai souvent cherché des solutions alternatives une fois mes exercices rendus et je me suis rendu compte que même en connaissant très bien le problème, j’avais parfois du mal à comprendre celles-ci, tant les possibilités et styles peuvent varier. Je trouve ça assez inquiétant pour la maintenabilité d’une application, en tout cas si personne ne prend le temps de bien fixer certaines règles. La liberté amenée par Scala vient avec un prix de rigueur, Odersky le dit lui même dans ses vidéos, n’hésitez pas à nommer les résultats intermédiaires de vos programmes. Et c’est vrai que l’API donne parfois envie de chaîner les appels sur une ligne, il faut donc résister et accepter de perdre en “oneliner” pour gagner en lisibilté.

– Les boucles “for comprehension” sont un concept puissant mais on se retrouve vite perdu entre les types disponibles, les itérateurs déclarés et les filtres. Alors que le code executé est souvent simple, toute la logique se trouve dans la déclaration du for. C’est souvent compliqué à débugger, parfois correct mais incroyablement lent si vous avez fait un mauvais choix (dont on ne se rend pas toujours compte immédiatement). Pour les anagrammes j’avais par exemple une solution qui fonctionnait correctement mais qui mettait 30 secondes de plus pour un itérateur mal déclaré. Il doit y avoir des carnages dans certaines applications… Il est parfois plus simple de faire le code avec des “map” et des “filter” puis de convertir en “for comprehension” par la suite.

 

Si le sujet vous intéresse un autre article bien plus complet sur Scala et Coursera se trouve ici. Je ne peux qu’admirer l’excellence marketing de TypeSafe qui réussit encore une fois un très beau coup (après l’annonce récente de l’entrée de Rod Johnson au board par exemple). Et à titre personnel, je suis content d’avoir participer : j’ai appris de nouvelles choses et voir d’autres langages est toujours un excellent moyen de progresser. Je ne suis par contre toujours pas convaincu pour faire un vrai gros projet de la vraie vie avec. Ou alors avec un mec de chez Twitter ou Foursquare qui vient m’aider à démarrer et à prendre les bonnes options et les bonnes pratiques. Mais on en a rarement sous la main. Je surveille quand même du coin de l’oeil, on ne sait jamais…