Best Practices AngularJS

AngularJS-large

 

Bien que la première version stable d’Angular2 soit sortie mi-septembre 2016, AngularJS reste très populaire parmi les développeurs front-end. Sa stabilité et sa puissance ne sont plus à démontrer et sont des alliés de poids dans la création d’applications à l’interface riche et aux nombreuses interactions avec l’utilisateur.

Néanmoins, la prise en main de cette technologie peut s’avérer longue et frustrante, notamment à cause de concepts tels que les directives ou encore le « two-way data binding ». Lorsque le framework n’est pas complètement maîtrisé, il en résulte une application peu optimisée voire trop lente dans certains cas, et dont le code n’est pas clair.

Nous allons donc voir dans cet article quelques best practices permettant d’optimiser les performances et la maintenabilité d’une application utilisant AngularJS.

Modules

  • Utiliser des conventions de nommage uniques avec des séparateurs pour les sous-modules afin de créer une hiérarchie.

Par exemple, app peut être le module racine, app.dashboard et app.users peuvent être des modules utilisés comme dépendances de app.

  • Déclarer les modules sans la variable (setter syntax), elle ne sera pas utile avec un seul composant par fichier.
angular
  .module('app', [
    'ngRoute',
    'ngDraggable',
    'ngAnimate',
    'app.dashboard',
    'app.users'
  ]);
app.js
  • Appliquer le même principe lorsqu’on souhaite utiliser un module : ne pas utiliser de variable mais plutôt un chaînage avec la getter syntax.
angular
  .module('app')
  .controller('MyController', MyController);

function MyController() { }
my.controller.js

Responsabilité unique

  • Ne définir qu’un composant par fichier, ce dernier ne doit pas dépasser 400 lignes. Cela facilite les tests unitaires, améliore la lisibilité et la maintenabilité du fichier, et évite des conflits dans certains systèmes de versionning.
angular
  .module('app', ['ngRoute, ngDraggable, ngAnimate']);
app.module.js
angular
  .module('app')
  .controller('MyController', MyController);

function MyController() { }
my.controller.js
angular
  .module('app')
  .factory('myFactory', myFactory);

function myFactory() { }
my.factory.js

 

Fonctions courtes

  • Les fonctions définies ne doivent pas dépasser 75 lignes et ne doivent avoir qu’un seul but. Cela permet de les réutiliser, de faciliter les tests et d’éviter les bugs que rencontrent les fonctions imposantes qui partagent des variables avec des scopes externes.

Scope en JavaScript et mode strict

  • Englober les composants dans des IIFE (Immediately Invoked Function Expression) afin de retirer ces variables du scope global. Cela prévient les collisions de variables lors de la minification du code et évite que celles-ci ne soient définies plus longtemps que nécessaire.
  • Utiliser le mode strict en plaçant l'expression 'use strict' à l'intérieur de fonctions ou de fichiers JS. Ce mode élimine quelques erreurs silencieuses en levant une exception lorsqu'elles sont rencontrées. Il facilite également l'optimisation du code pour les moteurs JS, rendant l'exécution plus rapide.
(function() {
  'use strict';

  angular
    .module('app')
    .factory('dashboardService', dashboardService);

  function dashboardService() { }
})();
dashboard.service.js

Cette syntaxe n'est pas respectée dans les autres exemples de code à des fins de concision.

Controllers

  • Le rôle premier des controllers est de récupérer les données d'un service (factory ou provider) et de les transmettre ensuite à la vue qui les affichera. Les controllers doivent donc contenir peu ou pas de logique. En effet, en exposant la logique dans les services (via une fonction), elle est utilisable par tous les controllers tout en réduisant le nombre de dépendances de ces derniers. Les controllers s'en trouvent également clarifiés et dédiés à une tâche simplifiée.
  • Utiliser la syntaxe ControllerAs. Elle se définit dans les controllers en capturant le contexte this dans une variable.
function DashboardController() {
  var vm = this;
  vm.title = 'Un titre';
}
dashboard.controller.js
<div ng-controller="DashboardController as vm>
  <input ng-model="vm.title" />
</div>
dashboard.html

Par convention, cette dernière se nomme vm (ViewModel), mais dans les applications à plus grande échelle, un nom de variable en rapport avec le contexte sera préférable (project, user, costumer, …).

Cette variable a pour but de remplacer $scope par un nom en rapport avec la fonctionnalité du controller. Ceci facilite la lisibilité des variables et empêche d’utiliser $parent dans les vues.

N’utiliser $scope que lorsque c’est absolument nécessaire, par exemple pour l’utilisation d’évènements tels que $emit, $broadcast ou $on.

Lors de l'utilisation de ngRoute pour la partie routing de l'application, la syntaxe ControllerAs se fait comme suit :

$routeProvider
  .when('/dashboard', {
    template: 'partials/dashboard.html',
    controller: 'DashbboardController',
    controllerAs: 'dashboard'
  });
app.js
  • Ne pas utiliser $scope ou $rootScope pour faire transiter des données entre controllers. Privilégier l’utilisation d’une factory qui sera ensuite appelée par les controllers.
  • Placer les membres bindables (variables et fonctions) en haut du controller, en ordre alphabétique. Cela améliore la maintenance et lisibilité.
function DashboardController() {
  var dashboard = this;

  dashboard.init = init;
  dashboard.save = save;
  dashboard.title = 'title';

  function init() {
    /* function code */
  }

  function save() {
    /* function code */
  }
}
dashboard.controller.js

Factories

  • Placer les membres du service appelables en haut du fichier pour faciliter la lecture du fichier et savoir quelles fonctions sont exposées.
  • Déclarer les fonctions au lieu de les assigner à une variable permet de les utiliser avant qu'elles ne soient définies.
function dashboardservice($http, $location, $q) {
  var myBool = true;

  var service = {
    getTasks: getTasks,
    getProjectLoad: getProjectLoad
  };

  return service;

  function getTasks() {
    /* function code */
  }

  function getProjectLoad() {
    /* function code */
  }
}
dashboard.service.js

Laisser un commentaire

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

Captcha *