Angular 1.5 : un pas de plus vers les web-components

Ce post a été écrit à la lumière du changelog d'Angular 1.5.0 rc1.
Toutes information est succeptible d'évoluer au cours des publications de nouvelles versions du framework.

#Le petit nouveau : .component()

Avec l'arrivée imminente de la version 2, Angular commence lentement à préparer la transition et apporte de nombreux changements à la v1 pour tenter de combler l'écart entre les deux et rendre les changements moins pénibles.

On voit donc progressivement apparaitre de nouvelles fonctionnalités comme :

Mais le point qui nous intéresse particulièrement ici n'est autre que le nouveau helper permettant la déclaration de similis composants web, aka web-components.

Les habitués connaissaient déjà angular.directive(), un helper permettant la déclaration de composants réutilisables. angular.directive() s'est donc enrichi au cours des années en faisant un atout incontournable du framework de par sa flexibilité et sa simplicité.

Mais toutes ces possibilités de déclaration n'étaient plus vraiment en phase avec les spécifications du W3C, angular.component() se présente donc un retour aux sources.

#De .directive() à .component()

Pour comprendre à quoi on arrive, il faut savoir de quoi on est parti. De toute évidence vous ne pourrez pas transformer toutes vos vieilles directives en composants web, du moins, pas sans compromis.

Pour rappel une directive Angular est définie par un objet JavaScript pouvant comporter les attributs suivants :

var directiveObj = {
  template          : '<div></div>',
  transclude        : false,
  restrict          : 'EA',
  scope             : false,
  bindToController  : false,
  controller        : function () { },
  controllerAs      : 'stringIdentifier',
}

#Restriction à la forme élémentaire

Ceux qui utilisent les directives Angular de manière régulière ne sont pas sans savoir qu'il est possible de les instancier de 4 manières différentes dans son HTML en modifiant l'attribut restrict :

  • comme un élément avec restrict : 'E'
  • comme un attribut avec restrict : 'A'
  • comme une classe avec restrict : 'C' (déconseillé)
  • comme un commentaire avec restrict : 'M' (fortement déconseillé)

On peut aussi autoriser l'utilisation mixe en combinant les lettres : restrict : 'EAC'

restrict n'est donc plus configurable et est restreint (sans mauvais jeu de mot) à la forme 'E' en faisant un composant de façon claire.

#Isolement du scope

Contrairement à .directive(), .component() force l'isolement du scope, ainsi on colle à la specification : le composant web est agnostique du contexte.

l'attribut scope est donc forcé à {} et n'est plus configurable.

#Passage de paramètres via bindings

La propriété scope n'étant plus disponible component. Il faut à présent utiliser la propriété bindings. La syntaxe de celle-ci est équivalent à celle de la propriété scope. Mais les éléments passés sont automatiquement attachés à l'instance du contrôleur lié au component.

Notons, que s'il reste possible d'utiliser la syntaxe = (two-way data binding), celle-ci est déconseillée au profit de la syntaxe < (one-way data binding).

#Utilisation forcée de controllerAs

Déjà présenté comme une best practice, controllerAs fait son chemin de manière évidente jusqu'au nouveau helper .component() son utilisation va de pair avec celle de bindings.

L'attribut, qui prend une chaine de caractères pour valeur, est à présent facultatif. S'il est omit le controller est automatiquement aliassé par l'objet $ctrl.

#Exemple de migration

Prenons l'exemple d'une directive simple et transformons la en composant Angular pour mettre en évidence l'ensemble des changements :

<hello-world name="World"></hello-world>

La syntaxe directive :

angular.directive('helloWorld', function helloWorld () {
  return {
    restrict          : 'E',
    scope             : {},
    bindToController  : {
      name : '@'
    },
    controller        : function helloWorldCtrl () {
      this.logName = function () {
        console.log(this.name);
      }
    },
    controllerAs      : 'hw',
    template          : '<div><span ng-click="hw.logName()">Hello {{hw.name}}!</span></div>'
  }
});

La syntaxe component :

angular.component('helloWorld', {
  bindings: {
    name: '@'
  },
  controller : function helloWorldCtrl () {
    this.logName = function () {
      console.log(this.name);
    }
  },
  template : '<div><span ng-click="$ctrl.logName()">Hi {{$ctrl.name}}!</span></div>'
});

Les changements sont légers mais permettent une lecture améliorée des attributs. Le composant est donc une version simplifiée de la directive, plus en phase avec la logique d'Angular 2.

Ce nouveau helper permet donc l'introduction progressive des web-components au sein d'Angular en vue de leur intégration et utilisation active dans la version 2.