DCube Lab : présentation d’AngularJS #1


angular-logo

Introduction :

Cet article fait suite au DCube Lab de ce mois-ci où nous avons lancé la première session de notre série AngularJS et qui fût animée par Félix Billon et moi-même.

Les DCube Labs étant des événements techniques basés sur la pratique d’une technologie, la structure sera assez différente des articles plus théoriques.

AngularJS ? C’est quoi ça encore ?!

AngularJS est un framework JavaScript créé en 2009 par une équipe de chez Google.

En peu de temps, il est devenu l’un des frameworks JavaScript les plus populaires du moment (ce qui lui donne un avantage indéniable : la communauté) pour tout ce qui est des Single Page Applications.

Le projet AngularJS est Open Source et disponible sur GitHub.
Il est supporté par les navigateurs modernes (desktop ou mobile), sauf IE8 qui ne l’est plus depuis la version 1.3.

Ce framework n’est pas là pour abstraire quoi que ce soit, vous continuez à faire du HTML, du CSS ou encore du JS comme bon vous semble.
Il vient simplement se greffer à votre application Web pour fournir une structure et des fonctionnalités que l’on envisageait jusque là uniquement côté serveur.
Il s’agit d’un framework MVC-like, ce qui signifie que le code est organisé autour de Models, de Views et de Controllers.

On n’avait pas parlé de pratique ?!

On y vient ne vous inquiétez pas.

Afin de se concentrer sur le développement côté front avec Angular, nous partirons d’un serveur Node.js existant.

Il est possible d’utiliser n’importe quelle technologie comme serveur (ASP.NET MVC, RubyOnRails, PHP, etc), mais nous avons préféré Node.js pour la rapidité de mise en place, pour rester dans de l’open source et en plus, on fait du FullStack JavaScript.

Il va donc falloir récupérer Node.js pour lancer le serveur.
Vous trouverez le projet de départ sur le GitHub de la présentation.

Si vous êtes familier avec Git, vous pouvez cloner le repository de suite, sinon il vous suffit de télécharger le fichier .zip généré par GitHub.

Download GitHub repository

Le repository est structuré de la façon suivante :

Repository structure

#1TodoManager : début du projet

Le projet de départ est contenu dans le dossier TodoManager (il s’agit de la copie de l’étape 1 du dossier “Steps”).

Il est possible de passer directement à l’étape qui vous intéresse si vous le souhaitez.

Comme vous l’aurez compris au nom du dossier de départ, la série de présentations AngularJS se fera sur la réalisation d’un manager de tâches.

Jetons un œil à la structure du projet en lui-même maintenant.
Pour ma part, j’utilise WebStorm comme éditeur JavaScript, mais libre à vous d’utiliser celui que vous voulez.

Project structure

Je vous laisse vous renseigner quant à la structure d’un serveur Node.js.
Ce qui nous importe là, c’est le dossier “/public” qui contient tous les éléments qui seront utilisés dans l’application Web, mais aussi le fichier index.html (dossier /app/views/) que le serveur nous délivrera.
En effet Angular étant un framework Front, il nous est nécessaire d’avoir un point d’entrée récupéré à partir du serveur, c’est le rôle de la page index.html.

/app/views/index.html

/public/js/app/app.js

Ce projet de départ est là pour valider qu’au lancement du serveur, nous ayons bien une page Web disponible sur le http://localhost:1313/ qui affiche une <div> avec “Test” et que l’on retrouve dans la console JS un message “TEST”.
Le serveur se lance via la commande Shell suivante (à la racine du projet) :

#2A nous de créer notre application AngularJS maintenant

Vous remarquerez que les bibliothèques Angular sont déjà présentes dans le dossier “/public/js/thirdParty”. Il ne nous reste plus qu’à ajouter la référence à Angular dans la page index.

/app/views/index.html

A partir de là, on va déclarer un module Angular que nous nommerons “todo.app”.

/public/js/app/app.js

Vous allez me dire : « c’est quoi ce concept de module  ? ».
Ce à quoi je répondrais instinctivement : « RTFM » (joke inside).

Plus sérieusement : AngularJS fonctionne en module, qui peuvent chacun avoir leurs dépendances.
Cela permet de ne pas avoir de point d’entrée prédéfini dans l’application et de faciliter la création de tests unitaires en ne chargeant que les modules à tester.
On pourrait le vulgariser et le comparer à un Namespace.

La création d’un module nécessite de passer un tableau de dépendances en deuxième paramètre de la méthode “angular.module”.
Si vous omettez ce paramètre, Angular interprétera cet appel comme le souhait de récupérer un module existant (ce qui, dans notre cas, engendrera inévitablement une erreur).
Nous reviendrons plus tard sur l’utilité de ce tableau de dépendances.

Une fois le module créé, il nous suffit d’indiquer à AngularJS le contenu HTML sur lequel il doit lier notre application.
Et ça se fait de la manière suivante :

/app/views/index.html

Nous venons de dire à Angular que notre body et tout ce qu’il contient doivent être surveillés et que ces éléments peuvent interagir avec le module “todo.app”.
Le module qui est fourni à l’attribut “ng-app” devient notre point d’entrée de notre application Angular (il est celui que l’on désignera par l'”application”).

Pour valider que le lien entre la page index.html et l’application est fonctionnel, nous allons nous attaquer aux étapes qui constituent le lancement de l’application.

  • la configuration
  • l’exécution

La configuration des différents modules ne peut se faire que lors de l’étape de configuration de l’application.
Ce sujet sera abordé lors du prochain DCube Lab et donc dans l’article qui lui sera consacré.

Nous allons associer des fonctions aux deux étapes citées au-dessus afin de valider l’ordre d’exécution.
Par la suite, elles nous seront utiles pour agir sur le comportement de l’application.

/public/js/app/app.js

Vous pouvez maintenant rafraichir le site afin de visualiser l’ordre des messages dans la console JavaScript.

Bravo, vous venez de créer votre première application AngularJS !

#3Une application c’est bien. Avec des données c’est mieux !

Maintenant que notre application est prête, nous allons voir le fonctionnement des controllers.
Avant toute chose, il faut savoir comment les controllers et les views échangent des données et leurs états.

C’est très simple, tout passe par ce que l’on appelle un Scope.
Ce scope va contenir toutes les données à lier à la view, mais également les différentes fonctions qui pourront être appelées par celle-ci.

Le controller va initialiser le scope qui sera lié à sa view (complète ou partielle).

Rien de mieux que la pratique pour saisir un concept ! Alors, créons un controller !

Pour cela, commençons par ajouter un fichier dans le dossier “/public/js/app/controllers” que nous nommerons “dashboardController.js”.

Tel qu’évoqué plus tôt dans cet article, AngularJS fonctionne par module : nous allons donc respecter les bonnes pratiques et ajouter un module pour nos controllers. Il aura pour nom “todo.controllers”.

/public/js/app/controllers/dashboardController.js

Il est possible d’ajouter des controllers aux modules Angular.

Ça tombe bien, C’est exactement ce que nous souhaitions ! Cela se fait par le biais de la méthode “controller” de notre module.

On lui passe le nom du controller et la fonction qui le définit et qui permettra l’initialisation de notre scope.

Alerte bonne pratique: Un controller étant considéré comme un constructeur en JavaScript, la même convention de nommage s’applique : adieu le camelCase et hello PascalCase.

« Ok controllers, ça j’ai saisi ! Mais on n’arrête pas de parler de scope, il est où celui là ?! »

Toujours aussi pressés ces développeurs… Nous arrivons à l’utilisation du scope.

Bon, ok j’ai simplement rajouté des paramètres… Mais c’est plus que ça !

En effet, Angular intègre un autre concept qui est l’injection de dépendances.
Il suffit de lui demander et si l’objet recherché est référencé par le framework, il vous le fournira.

Dans notre cas, nous lui demandons deux objets : le rootScope et le scope.

Un scope peut être vu comme un simple objet sur lequel vous pouvez ajouter n’importe quelle propriété.
Chaque controller a un scope qui lui est propre et qui n’est pas accessible par les autres controllers.
Cependant, pour répondre à certains besoins tels que détenir des informations globales à l’application, un rootScope est disponible.
Il s’agit d’un scope identique aux autres, à la différence près que tous les scopes utilisés dans l’application hériteront du rootScope.

Dans la plupart des controllers, nous n’auront pas besoin du rootScope, mais nous allons tout de même voir en pratique ses caractéristiques énoncées ci-dessus.

Le préfixe des noms de variables avec “$” définit en général des objets relatifs aux modules tiers AngularJS.

Maintenant que notre controller est déclaré et avant de définir son contenu, rappelez-vous que nous sommes dans un autre module : il va bien falloir le rattacher à notre application.
Il nous suffit de rajouter notre module de controllers dans les dépendances du module de l’application.

/public/js/app/app.js

On assigne une variable “title” à notre scope afin de l’utiliser dans la view.

/public/js/app/controllers/dashboardController.js

À partir de là, on peut assigner notre controller à un élément HTML de la view grâce à l’attribut “ng-controller”, ce qui rendra son scope accessible par tous les éléments enfants.

/app/views/index.html

N’oubliez pas d’ajouter la référence à notre script dans la view !

Rajoutons deux balises qui afficheront notre titre, mais de deux manières différentes.

/app/views/index.html

Le scope étant lié aux éléments HTML, on peut accéder directement aux propriétés via leur nom.

Alors, que venons-nous de placer dans notre HTML ?

  • Le « {{ property }} » définit une expression AngularJS dans du HTML.
    L’expression est faite en JavaScript et elle est interprétée par le framework lors de son passage sur la view.
  • L’attribut « ng-bind=”property” » va quant à lui contenir une expression JavaScript qui n’a pas besoin d’être “décorée” (par les doubles brackets).
    En effet, l’attribut étant spécifique à AngularJS, il sait par défaut qu’il doit traiter son contenu.

L’expression « {{ }} » peut poser des soucis en fonction de la vitesse de chargement de votre view.
Étant interprété au passage d’AngularJS sur la view, si le controller met du temps à charger, vous verrez apparaître votre expression en texte pur pendant un court instant.
Privilégiez l’utilisation de l’attribut ng-bind tant que cela ne nuit pas à la lisibilité dû à une expression trop longue.

#4Utilisation du rootScope et multiples controllers

Puisque nous devons ajouter un autre controller dans cette étape, nous allons respecter une autre bonne pratique qui est de séparer la déclaration des modules de l’assignation de leurs composants (controllers ou tout autre élément que nous aborderons par la suite).

Créons un fichier “controllerModule.js” dans notre dossier controllers qui contiendra la déclaration de notre module.

/public/js/app/controllers/controllerModule.js

Notre dashboardController devient donc :

/public/js/app/controllers/dashboardController.js

Pour approfondir le fonctionnement des scopes et plus particulièrement du rootScope, nous allons initialiser un utilisateur au lancement de l’application.

/public/js/app/app.js

De retour sur le dashboardController et afin d’afficher dans la view des informations sur l’utilisateur, nous allons ajouter une propriété userName au scope.
Sauf que nous allons récupérer les informations utilisateurs contenues dans le scope ET dans le rootScope afin d’illustrer l’héritage des scopes.

/public/js/app/controllers/dashboardController.js

Très bien, testons notre controller !

On va donc modifier un peu notre view.

/app/views/index.html

En lançant notre site, on peut donc visualiser que les données prises à partir du scope sont identiques à celles du rootScope.

Le scope peut accéder aux propriétés définies dans le rootScope du moment qu’on ne lui assigne pas de propriété du même nom cachant celle parent.

C’est le moment d’ajouter notre second controller qui aura pour but de fournir à la view les informations relatives à nos tâches.
Il nous faut donc créer un nouveau fichier dans le dossier controllers nommé “todoController.js”.

Vous connaissez la chanson, je vous laisse créer le controller qui nous générera notre tableau de tâches.

/public/js/app/controllers/todoController.js

Il ne nous reste plus qu’à intégrer ce controller à la view.

Nous allons utiliser un nouvel attribut nommé “ng-repeat” qui itère sur une collection d’objets et va générer l’élément html sur lequel il est placé pour chaque itération.

/app/views/index.html

Oui, les controllers peuvent être imbriqués dans les views. Si vous essayez d’accéder à une propriété d’un scope, vous aurez la valeur contenue dans le plus “proche” scope qui possède cette propriété.

Vous devriez vous retrouver avec la page ci-dessous :

Download GitHub repository

#5Les filtres, ou comment formater et dynamiser ses données.

Le but de cette étape est de dynamiser notre liste de tâches.
Voici ce sur quoi nous allons travailler :

  • ajouter une notion de priorité à nos tâches afin de pouvoir filtrer sur la priorité souhaitée.
  • pouvoir trier notre liste de tâches.

Pour définir cette notion de priorité, nous allons mettre en place de la configuration.
On crée un fichier “app.config.js” dans le dossier “/public/app/config”.
On en profite pour déclarer un nouveau module qui servira à la configuration et les valeurs que l’on souhaite définir seront stockées dans une autre catégorie d’éléments qui peuvent composer un module : les constantes.
Comme leur nom l’indique, ce sont des objets ou valeurs qui garderont la même valeur tout au long de la vie de l’application. C’est ce qui les rend parfaites pour de la configuration.

On définit les valeurs possibles de nos priorités en déclarant un objet qui sera utilisé comme un Enum pour ceux qui ont l’habitude (en .NET par exemple).

/public/js/app/config/app.config.js

Afin de pouvoir utiliser cette configuration dans nos controllers, il est nécessaire d’ajouter la référence du module de configuration à notre module controllers.

/public/js/app/controllers/controllerModule.js

Maintenant que nous avons notre configuration associée au module “controllers”, il nous est possible de récupérer notre constante définissant les priorités.
Rappelez-vous, nous utilisons l’injection de dépendance pour les paramètres. Il suffit qu’un module soit ajouté aux dépendances pour pouvoir récupérer tous ses composants directement en paramètre.

/public/js/app/controllers/todoController.js

Maintenant il nous faut prévoir les propriétés du scope qui nous permettrons de gérer le filtre et le tri des priorités :

  • todoPriority qui nous permettra d’utiliser notre objet de définission des priorités directement dans la view.
  • priorityFilter qui sera l’objet de filtre (oui AngularJS permet de filtrer à partir d’un objet et de ne garder que les éléments correspondant complétement à ses propriétés)
  • sortOrder définira le nom de la propriété sur laquelle nous souhaitons trier les tâches (dans notre cas, son identifiant).
  • getPriorityName est une fonction qui nous donnera le libellé associé à notre priorité.

/public/js/app/controllers/todoController.js

Mettons à jour la view pour intégrer l’utilisation des tris et des filtres.
Nous aurons des boutons pour les actions et un titre qui intégrera le statut actuel du filtre/tri.

/app/views/index.html

C’est maintenant que l’on voit un nouvel élément d’AngularJS : les filtres.
On les utilise dans les views comme ceci : « variable | [filtre] » (et ils peuvent s’enchaîner !).
Les filtres peuvent avoir plusieurs comportements :

  • le filtre pur qui prend une collection en entrée et qui renvoie ce même objet filtré (e.g. ‘filter’).
  • le filtre de tri qui va réordonner une collection (e.g. ‘orderBy’).
  • le filtre de formattage qui va traiter une valeur pour l’afficher d’une certaine façon, dans un format spécifique (e.g. ‘number:[value]’ qui va afficher le nombre avec [value] chiffres après la virgule).

Dans notre cas, nous avons besoin de filtrer sur une priorité, mais aussi de trier en fonction du contenu de la variable sortOrder.
Nous utiliserons donc les filtres ‘filter’ et ‘orderBy’.

/app/views/index.html

Tout comme les constantes et les controllers, il est possible de créer ses propres filtres pour tout besoin de comportement spécifique.

À partir de là, il ne nous reste plus qu’à ajouter des actions à nos boutons pour changer les valeurs du filtre et du tri.
En fonction des boutons cliqués, nous associerons la valeur correspondante à nos propriétés de filtre.

/app/views/index.html

Pour éviter la surcharge d’une présentation déjà bien fournie, les changements de valeurs sont réalisés directement dans l’attribut ng-click.
Dans un cas concret et pour garder un code propre, bien organisé et en accord avec les bonnes pratiques, il faudrait que les ng-click soient associés à des fonctions définies dans le scope.

À partir du composant $filter récupérable en paramètre de notre controller, il est possible d’utiliser les filtres côté controller.

L’exemple ci-dessous démontre le fonctionnement. Le filtre que vous mettriez à la droite du « | » dans vos views, il suffit de le fournir en appelant $filter(‘nom’).
N’étant qu’une illustration de ce qui peut se faire, la démonstration se fait via un log dans console JavaScript.

/public/js/app/controllers/todoController.js

Voici le visuel que vous devriez avoir à la fin de cette étape.

Download GitHub repository

C’est déjà la fin ?!

Malheureusement, les gens présents lors de la première session n’étaient pas des flèches, vous comprendrez que nous n’ayons pas eu l’occasion de voir plus…

N’hésitez pas à poster vos remarques ou questions.
Je vous donne rendez-vous pour la prochaine session !

Commentez cet article