Il y a 2 années · 10 minutes · Front

Angular 2 – Sous le capot

Grande nouvelle, la team d’Angular a étiqueté la version 2.0.0-alpha.55 en version Beta ! Par ce changement, Google nous fait comprendre que le framework est enfin stabilisé !

Lors de la présentation d’Angular 2, nous vous présentions succinctement les concepts généraux d’Angular 2.
Son passage en version Bêta est le moment idéal de vous détailler la syntaxe ainsi que la pierre angulaire du framework : les composants !

Composants

Dans Angular 2, contrôleurs et scopes ont disparu. Dès lors, comment construit-on nos applications ? Nous utilisons les composants ! Ces derniers sont partout et constituent un élément central pour toute application écrite en Angular 2.

Les composants ont un cycle de vie et une API définis strictement et sont autoporteurs / autonomes, c’est-à-dire qu’ils décrivent de manière exhaustive sous forme de configuration, l’ensemble de leurs interactions avec l’extérieur ainsi que leurs dépendances internes. C’est cette particularité qui est approfondie dans la suite de l’article.

Arborescence des composants

Une application Angular 2 est constituée de composants imbriqués qui, ensemble, forment une structure arborescente. Chaque application Angular 2 a toujours un seul composant racine dont dépendent tous les autres. Ci-dessous, une représentation typique d’une arborescence de composants :

Arborescence de composants Angular 2

Déclaration d’un composant

Un composant en Angular 2 est une simple classe Javascript. Pour indiquer à Angular que cette classe est un composant, nous utilisons les « décorateurs » de composants (vulg. annotation). Ce décorateur permet de décrire entrées / sorties / cycle de vie du composant en question.

@Component({
  selector: 'my-component'
})
class MyComponent {
}

Nota bene : tous les exemples de code sont écrits en TypeScript (cf. Angular 2 – Présentation).




<div>
  <my-component></my-component>
</div>




Déclaration d’une vue

Chaque composant a besoin d’un template afin de définir sa représentation sur la page. C’est le rôle du décorateur View. Ce dernier est relativement flexible étant donné qu’il nous laisse la possibilité d’écrire la vue : soit directement dans le Javascript, au plus près de la classe du composant ; soit dans une page à part.

Exemple de template inline :

@Component({ selector: 'my-component'})
@View({
  template: "Hello {{message}} !"
})
class MyComponent {
  message:string = 'World';
}

… ou multi-lignes en utilisant des back-quotes (syntaxe ES6) :

@View({
  template: `
    
      Hello {{message}} !
    
  `
})

Pour les plus curieux, sachez qu’il existe un raccourci d’écriture qui permet de se passer du décorateur View en poussant l’ensemble des attributs de View vers le décorateur Component !

Exemple de template dans un fichier séparé :

@View({
  templateUrl: 'my-component.html'
})

… et le contenu de son fichier séparé :

Hello {{message}} !

À noter la présence des moustaches, rescapées de la première mouture du framework. Cette syntaxe permet de lier le modèle du composant à sa vue. À la différence d’Angular 1, ce modèle n’est plus rattaché au scope, mais directement à l’instance de classe du composant (dans notre exemple, le modèle est représenté par l’attribut « message » du composant).

Conformité avec les WebComponents

Angular 2 est conforme aux standards des WebComponents. Ainsi, il inclue la vue d’un composant dans le Shadow DOM de l’élément au sein duquel il est inséré. Par ailleurs, si le navigateur ne supporte pas le Shadow Dom, Angular va l’émuler en utilisant les shims et les polyfills nécessaires.

API Entrée / Sortie

Properties : [ ]

Les propriétés constituent les API d’entrée des composants.

Tout d’abord, un petit rappel de ce qui différencie attributs et propriétés HTML :

<input type="text" value="foo"> <!-- attributs value et type -->

Quand le navigateur interprète cette balise, il crée un objet node en mémoire :

input:
  ...
  value: "foo" // <-- propriétés value et type
  type: "text"
  ...

Pour chaque attribut, il créé une propriété en mémoire en utilisant la valeur d’origine de l’attribut de l’input dans le DOM. Si l’on saisit quelque chose dans cet Input, c’est la valeur de la propriété value qui sera modifiée. L’attribut value, quant à lui, restera inchangé. Les valeurs d’attribut ne servent qu’à l’initialisation de l’objet node.

Angular 1 travaille avec les attributs :

<img src="{{myImage}}">

Ce code pose un problème. Avant même que le javascript soit exécuté, le navigateur va tenter de chercher l’image à l’adresse {{myImage}} ce qui va résulter en erreur « 404 Not Found ». Ce n’est qu’au moment où Angular 1 prend la main et remplace la variable myImage par une vraie URL que l’image sera correctement récupérée par le navigateur. Pour palier ce problème, Angular 1 nous met à une disposition une directive spéciale : ng-src.

Angular 2 travaille directement avec les propriétés en introduisant une nouvelle syntaxe (utilisation des crochets) :

<img [src]="myImage" />

Le présence des crochets à gauche signifie que l’on va trouver une expression Angular à droite. Une fois évaluée, le résultat ce cette expression sera directement écrit dans la propriété src correspondante.

Prenons un autre exemple :

Bloc de code écrit en Angular 1 :




<div ng-hide="isHidden">This div will be hidden if isHidden is true</div>




<my-component foo="{{something}}"></my-component>

Bloc de code équivalent, écrit en Angular 2 :




<div [hidden]="isHidden">This div will be hidden if isHidden is true</div>




<my-component [foo]="something"></my-component>

Une des conséquences directes de cette transformation est la disparition des directives ng-hide, ng-show, ng-disabled, ng-bind, etc. Angular 2 propose un comportement unifié et sa syntaxe « couvre » l’ensemble des attributs, tant anciens que nouveaux, sans qu’il ne soit nécessaire d’ajouter de nouvelles directives.

Revenons à la classe de notre composant. Chacune de ses propriétés doit être déclarée. C’est-à-dire que les propriétés doivent être explicitement ajoutées en tant qu’attributs de la classe du composant. Voilà pourquoi nous parlons d’API strictement définie. Un coup d’œil aux annotations et aux attributs du composant nous permet immédiatement d’en connaître toutes les interactions possibles.

Les propriétés peuvent être directement déclarées dans le décorateur @Component ou par le biais du décorateur @Input :

@Component({
  selector: 'my-component',
  inputs : ['model'] // <-- première syntaxe
})
class MyComponent {
  @Input() model:any; // <-- seconde syntaxe
  show() {
    console.log(this.model);
  }
}
<my-component [model]="data"></my-component>

Dans cet exemple, la propriété model se verra attribuer la valeur de la variable data. Cette valeur sera alors accessible depuis this.model à l’intérieur de notre composant.

Events: ( )

Les événements constituent les APIs de sortie des composants.

Prenons d’abord l’exemple en Angular 1 :

<my-component select="myFunction(name)"></my-component>

Les événements d’Angular 1 nous posaient un certain nombre de problèmes. Dans l’exemple ci-dessus : est-ce que myFunction(name) est une expression Angular qui sera évaluée sur le $scope et son résultat assigné à l’attribut select ? Ou est-ce un callback qui sera exécuté à l’intérieur de la directive ? Il est impossible de le savoir sans regarder dans le code source de la directive my-component.

Par le biais de parenthèsesAngular 2 introduit une nouvelle syntaxe de remontée d’événements :

<my-component (select)="myFunction(name)"></my-component>

L’ambiguïté est enfin levée ! Maintenant, si l’attribut est encadré par des parenthèses, nous savons que nous sommes en présence d’un événement, et pas d’une propriété. Un effet secondaire de ce changement, c’est la disparition de directives dorénavant inutiles : ng-click, ng-blur, ng-change, etc. qui reposent désormais sur la même écriture syntaxique : ng-click=... devient (click)=....

Cette syntaxe est donc applicable aux événements personnalisés ET aux événements natifs du DOM.

Par exemple, prenons ce fragment de code écrit en Angular 1 :




<div ng-click="doSomething()"></div>




La même chose en Angular 2 aura la forme suivante :




<div (click)="doSomething()"></div>




Ici, nous avons utilisé l’événement natif click. Une fois l’événement remonté par le composant (balise div) l’expression doSomething() sera évaluée. Par ailleurs, il est intéressant de noter que si doSomething n’existe pas, Angular sera (enfin !) en mesure de lever une exception intelligible.

A l’instar des propriétés, les événements doivent être déclarés. Nous pouvons le faire dans le décorateur @Component à l’aide de la propriété outputs: [] ou bien en utilisant le décorateur @Output :

@Component({
  selector: 'my-component' 
})
class MyComponent {
  @Output() event:EventEmitter = new EventEmitter(); // <-- declaration de l'événement
  fireMyEvent(data:string) {
    this.event.next({value: data});
  }
}

A noter que l’EventEmitter est utilisé pour envoyer un événement vers le composant parent.

Memento « Propriétés » et « Evénements »

  • Les données entrent dans les composants via les “bindings” des propriétés (avec les crochets « […] »)
  • Les données sortent du composant via les “bindings” des événements (en utilisant les parenthèses « (…) »)

Memento Angular 2

Les bindings des propriétés et des événements sont les API publiques d’un composant.

Two way data binding

Comme nous venons d’expliquer, le binding de propriétés est utilisé pour passer les données du parent vers l’enfant, et le binding d’événements pour passer les données de l’enfant vers le parent. Dès lors, il nous est possible de combiner les deux pour implémenter le binding bidirectionnel :

<input [ng-model]="todo.text" (ng-model-change)="todo.text=$event"></input>

Étant donné que ce pattern est souvent utilisé, Angular propose un sucre syntaxique pour nous simplifier la vie :

<input [(ng-model)]="todo.text"></input>

Notez qu’à la différence d’Angular 1, nous sommes capable de différencier les bindings mono et bidirectionnels. Aucune des garanties d’Angular 2 n’est violée : il ne s’agit que d’un sucre syntaxique.

Le roi est mort, vive le roi !

Variables locales : #

Angular 2 introduit une nouvelle syntaxe pour créer les variables locales en utilisant le dièse « # ».

Dans l’exemple suivant, nous déclarons une variable locale user qui référence un nœud input :

<input type="text" #user (keyup)>

Nous pouvons ensuite utiliser cette variable dans le template pour accéder aux propriétés de ce nœud, voire appeler les méthodes qu’il expose :

{{user.value}}


  Grab focus


Il faut souligner que cette variable sera accessible uniquement à l’intérieur de ce template, et nulle part ailleurs.

Ce qu’il faut retenir !

Un composant d’Angular 2 :

  • sait interagir avec son élément hôte ;
  • sait comment se representer via son template ;
  • définit exhaustivement ses APIs publiques ;
  • suit un cycle de vie strict.

Les composants Angular 2 sont complètement autonomes. Ils contiennent toutes les informations nécessaires à leur execution. N’importe quel composant peut être démarré en tant qu’application principale. Par conséquent, ils peuvent être empaquetés et distribués pour être utilisés dans d’autres frameworks (par exemple Ember.js ou React.js).

À venir…

Injection de dépendance, gestion de routes, tests, etc. Bien d’autres choses palpitantes nous attendent encore ! A découvrir dans l’article Angular 2 en Action !

Alexandre Hebert

Développeur Full Stack depuis 3 ans, Alexandre a une connaissance approfondie de nombreux langages et frameworks. Curieux de comprendre le "comment ça fonctionne" plutôt que de simplement "utiliser", c'est avec cet état d'esprit qu'il évolue depuis plusieurs mois au sein d'une équipe agile de Xebia.

Retrouvez-le sur http://a1exand.re, et sur son compte twitter @dijxdream.

Dmytro Podyachiy
Développeur full stack passionné par le monde open source, Dmytro travaille depuis plusieurs années dans l’écosystème Java sur des missions innovantes. Évangéliste du framework Angular, il l’utilise depuis presque 3 ans. Intéressé par les langages différents, il utilise notamment Scala et Javascript coté serveur.

Laisser un commentaire

Votre adresse de messagerie ne sera pas publiée. Les champs obligatoires sont indiqués avec *