Skip to content

Latest commit

 

History

History
641 lines (518 loc) · 26.7 KB

README-fr-fr.md

File metadata and controls

641 lines (518 loc) · 26.7 KB

Introduction

Ce guide est la traduction francaise de AngularJS style guide.

Le but de ce guide de style est d'exposer un ensemble de meilleures pratiques et directives de style pour une application AngularJS. Elles proviennent:

  1. du code source d'AngularJS
  2. du code source ou des articles que j'ai lus
  3. de ma propre expérience.

Note 1: ce guide est encore à l'état d'ébauche. Son principal objectif est d'être développé par la communauté, donc combler les lacunes sera grandement apprécié par l'ensemble de la communauté.

Note 2: avant de suivre certaines directives des traductions du document original en anglais, assurez-vous qu'elles sont à jour avec la dernière version.

Dans ce document, vous ne trouverez pas de directives générales concernant le développement en JavaScript. Vous pouvez les trouver dans les documents suivants:

  1. Google's JavaScript style guide
  2. Mozilla's JavaScript style guide
  3. Douglas Crockford's JavaScript style guide
  4. Airbnb JavaScript style guide

Pour le développement d'AngularJS, le guide recommandé est Google's JavaScript style guide.

Dans le wiki Github d'AngularJS, il y a une section similaire de ProLoser, vous pouvez la consulter ici.

Table des matières

Général

Arborescence

Étant donné qu'une grosse application AngularJS a beaucoup de composants, il est préférable de la structurer en une hiérarchie de répertoires. Il existe deux approches principales:

  • Créer une division de haut niveau par types de composants et une division inférieure par fonctionnalité.

De cette façon, la structure de répertoires ressemblera à:

.
├── app
│   ├── app.js
│   ├── controllers
│   │   ├── home
│   │   │   ├── FirstCtrl.js
│   │   │   └── SecondCtrl.js
│   │   └── about
│   │       └── ThirdCtrl.js
│   ├── directives
│   │   ├── home
│   │   │   └── directive1.js
│   │   └── about
│   │       ├── directive2.js
│   │       └── directive3.js
│   ├── filters
│   │   ├── home
│   │   └── about
│   └── services
│       ├── CommonService.js
│       ├── cache
│       │   ├── Cache1.js
│       │   └── Cache2.js
│       └── models
│           ├── Model1.js
│           └── Model2.js
├── partials
├── lib
└── test
  • Créer une division de haut niveau par fonctionnalité et de niveau inférieur par type de composants.

Voici son schéma:

.
├── app
│   ├── app.js
│   ├── common
│   │   ├── controllers
│   │   ├── directives
│   │   ├── filters
│   │   └── services
│   ├── home
│   │   ├── controllers
│   │   │   ├── FirstCtrl.js
│   │   │   └── SecondCtrl.js
│   │   ├── directives
│   │   │   └── directive1.js
│   │   ├── filters
│   │   │   ├── filter1.js
│   │   │   └── filter2.js
│   │   └── services
│   │       ├── service1.js
│   │       └── service2.js
│   └── about
│       ├── controllers
│       │   └── ThirdCtrl.js
│       ├── directives
│       │   ├── directive2.js
│       │   └── directive3.js
│       ├── filters
│       │   └── filter3.js
│       └── services
│           └── service3.js
├── partials
├── lib
└── test
  • Dans l'éventualité ou le nom du dossier contient plusieurs mots, utilisez la syntaxe lisp-case comme suit:
app
 ├── app.js
 └── mon-module-complexe
     ├── controllers
     ├── directives
     ├── filters
     └── services

  • Lors de la création de directives, placez tous les fichiers associés à une directive (gabarits, fichiers CSS / SASS, JavaScript) dans un seul dossier. Si vous choisissez d'utiliser ce style d'arborescence, soyez cohérent et utilisez-le partout dans votre projet.
app
└── directives
    ├── directive1
    │   ├── directive1.html
    │   ├── directive1.js
    │   └── directive1.sass
    └── directive2
        ├── directive2.html
        ├── directive2.js
        └── directive2.sass

Cette approche peut être combinée avec les deux structures de répertoires ci-dessus.

  • Une dernière petite variation des deux structures de répertoires est celle utilisée dans ng-boilerplate. Dans celle-ci, les tests unitaires pour un composant donné sont dans le même dossier que le composant. De cette façon, quand vous modifiez un composant donné, il est facile de trouver ses tests. Les tests tiennent aussi lieu de documentation et démontrent des cas d'usage.
services
├── cache
│   ├── cache1.js
│   └── cache1.spec.js
└── models
    ├── model1.js
    └── model1.spec.js
  • Le fichier app.js devrait contenir la définition des routes, la configuration et/ou l'amorçage manuel (si nécessaire).
  • Chaque fichier JavaScript ne devrait contenir qu'un seul composant. Le fichier doit être nommé avec le nom du composant.
  • Utilisez un modèle de structure de projet pour Angular tel que Yeoman ou ng-boilerplate.

Les conventions sur le nommage des composants peuvent être trouvées dans la section de chaque composant.

Balisage

TLDR; Placer les scripts tout en bas.

<!DOCTYPE html>
<html lang="en">
<head>
  <meta charset="utf-8">
  <title>MyApp</title>
</head>
<body>
  <div ng-app="myApp">
    <div ng-view></div>
  </div>
  <script src="angular.js"></script>
  <script src="app.js"></script>
</body>
</html>

Garder les choses simples et placez les directives spécifiques d'AngularJS en dernier, après les attributs HTML standard. De cette façon, il sera plus facile de parcourir votre code et de le maintenir puisque vos attributs seront groupés et positionnés de manière cohérente et régulière.

<form class="frm" ng-submit="login.authenticate()">
  <div>
    <input class="ipt" type="text" placeholder="name" require ng-model="user.name">
  </div>
</form>

Les autres attributs HTML devraient suivre les recommandations du Code Guide de Mark Otto.

Conventions de nommage

The tableau suivant présente des conventions de nommage pour chaque élément:

Élément Style de nommage Exemple Usage
Modules lowerCamelCase angularApp
Contrôleurs Fonctionnalité + 'Ctrl' AdminCtrl
Directives lowerCamelCase userInfo
Filtres lowerCamelCase userFilter
Services UpperCamelCase User constructor
Factories lowerCamelCase dataFactory autres

Autres

  • Utilisez:
    • $timeout au lieu de setTimeout
    • $interval au lieu de setInterval
    • $window au lieu de window
    • $document au lieu de document
    • $http au lieu de $.ajax
    • $location au lieur de window.locationou $window.location
    • $cookies au lieu de document.cookie

Cela rendra vos tests plus facile à faire et, dans certains cas, évitera les comportements inattendus (par exemple, si vous avez oublié $scope.$apply dans setTimeout).

  • Automatisez votre flux de travail en utilisant des outils comme:

  • Utilisez des promises ($q) au lieu de rappels (callback). Cela rendra votre code plus élégant et propre, et vous sauvera de l'enfer des callbacks.

  • Utilisez $resource au lieu de $http lorsque possible. Un niveau d'abstraction plus élevé diminuera la redondance.

  • Utilisez un pré-minifier AngularJS (ng-annotate) pour la prévention des problèmes après minification.

  • N'utilisez pas de variables globales. Résolvez toutes les dépendances en utilisant l'injection de dépendances, cela préviendra les bugs et le monkey-patching lorsqu'en phase de test.

  • Eliminez les variables globales en utilisant Grunt/Gulp pour englober votre code dans des Expressions de Fonction Immédiatement Invoquée (Immediately Invoked Function Expression, (IIFE)). Vous pouvez utiliser des plugins tel que grunt-wrap ou gulp-wrap pour cet usage. Exemple (avec Gulp)

     gulp.src("./src/*.js")
     .pipe(wrap('(function(){\n"use strict";\n<%= contents %>\n})();'))
     .pipe(gulp.dest("./dist"));
  • Ne polluez pas votre portée $scope. Ajoutez uniquement sur celle-ci les fonctions et les variables qui sont utilisés dans les gabarits.

  • Préférez l'utilisation de contrôleurs au lieu de ngInit. Il n'y a que quelques utilisations appropriées de ngInit tel que pour spécifier des propriétés spécifiques de ngRepeat, et pour injecter des données via des scripts côté serveur. Outre ces quelques cas, vous devez utiliser les contrôleurs plutôt que ngInit pour initialiser les valeurs sur une portée. L'expression passée à ngInit doit être analysée et évaluée par l'interpréteur d'Angular implémenté dans le service $parse. Ceci mène à:

    • Des impacts sur la performance, car l'interpréteur est implémenté en JavaScript
    • La mise en cache des expressions passées au service $parse n'a pas réellement de sens dans la plus part des cas, étant donnée que l'expression ngInit n'est évaluée qu'une seule fois
    • Ecrire des chaînes de caractère dans votre HTML est enclin à causer des erreurs, puisqu'il n'y a pas d'auto-complétion ou de support par votre éditeur de texte
    • Aucune levée d'exceptions à l'éxécution
  • Ne pas utiliser le prefixe $ pour les noms de variables, les propriétés et les méthodes. Ce préfixe est réservé à l'usage d'AngularJS.

  • Lors de la résolution des dépendances par le système d'injection de dépendances d'AngularJS, triez les dépendances par leur type - les dépendances intégrées à AngularJS en premier, suivies des vôtres :

module.factory('Service', function ($rootScope, $timeout, MyCustomDependency1, MyCustomDependency2) {
  return {
    //Something
  };
});

Modules

  • Les modules devraient être nommés en lowerCamelCase. Pour indiquer que le module b est un sous-module du module a, vous pouvez les imbriquer en utlisant un espace de noms tel que a.b.

    Les deux façons habituelles de structurer les modules sont:

    1. par fonctionnalité
    2. par type de composant.

    Actuellement, il n'y a pas une grande différence entre les deux mais la première semble plus propre. En outre, si le chargement paresseux des modules est implémenté (actuellement il ne figure pas sur la feuille de route d'AngularJS), il permettra d'améliorer les performances de l'application.

Contrôleurs

  • Ne manipulez pas le DOM dans vos contrôleurs. Cela rendra vos contrôleurs plus difficiles à tester et violerait le principe de séparation des préoccupations. Utilisez plutôt les directives.

  • Le nom d'un contrôleur s'obtient à partir de sa fonction (par exemple panier, page d'accueil, panneau d'administration) suffixée par Ctrl.

  • Les contrôleurs sont des constructeurs javascript ordinaires, ils sont donc nommés en UpperCamelCase (HomePageCtrl, ShoppingCartCtrl, AdminPanelCtrl, etc.)

  • Les contrôleurs ne devraient pas être définis dans le contexte global (bien qu'AngularJS le permet, c'est une mauvaise pratique de polluer l'espace de noms global).

  • Utilisez la syntaxe suivante pour définir des contrôleurs:

    function MyCtrl(dependency1, dependency2, ..., dependencyn) {
      // ...
    }
    module.controller('MyCtrl', MyCtrl);

    Une telle définition évite les problèmes avec la minification. Vous pouvez générer automatiquement la définition du tableau à l'aide d'outils comme ng-annotate (et la tâche grunt grunt-ng-annotate).

    Une autre alternative serait d'utiliser $inject comme suit:

    angular
      .module('app')
      .controller('HomepageCtrl', Homepage);
    
    HomepageCtrl.$inject = ['$log', '$http', 'ngRoute'];
    
    function HomepageCtrl($log, $http, ngRoute) {
      // ...
    }
  • Utilisez les noms d'origine des dépendances du contrôleur. Cela vous aidera à produire un code plus lisible:

module.controller('MyCtrl', ['$scope', function (s) {
  //...body
}]);

est moins lisible que

module.controller('MyCtrl', ['$scope', function ($scope) {
  //...body
}]);

Cela s'applique particulièrement à un fichier qui a tellement de lignes de code que vous devrez les faire défiler. Cela pourrait vous faire oublier quelle variable est liée à quelle dépendance.

  • Faites les contrôleurs aussi simples que possible. Extrayez les fonctions couramment utilisées dans un service.

  • Communiquez entre les différents contrôleurs en utilisant l'appel de méthode (possible lorsqu'un enfant veut communiquer avec son parent) ou $emit, $broadcast et $on. Les messages émis et diffusés doivent être réduits au minimum.

  • Faites une liste de tous les messages qui sont passés en utilisant $emit et $broadcast, et gérez-la avec précaution à cause des conflits de nom et bugs éventuels. Exemple:

    // app.js
    /* * * * * * * * * * * * * * * * * * * * * * * * * * * * * *
    Custom events:
      - 'authorization-message' - description of the message
        - { user, role, action } - data format
          - user - a string, which contains the username
          - role - an ID of the role the user has
          - action - specific ation the user tries to perform
    * * * * * * * * * * * * * * * * * * * * * * * * * * * * * */
  • Si vous devez formater les données alors encapsulez la logique de mise en forme dans un filtre et déclarez-le comme dépendance:

module.filter('myFormat', function () {
  return function () {
    //body...
  };
});

module.controller('MyCtrl', ['$scope', 'myFormatFilter', function ($scope, myFormatFilter) {
  //body...
}]);
  • Il est préférable d'utiliser la syntaxe controller as:

    <div ng-controller="MainCtrl as main">
       {{ main.title }}
    </div>
    
    app.controller('MainCtrl', MainCtrl);
    
    function MainCtrl () {
      this.title = 'Some title';
    }

    Les principales avantages d'utiliser cette syntaxe:

    • Créer des composants isolés - les propriétés liées ne font pas parties de la chaîne de prototypage de $scope. C'est une bonne pratique depuis que l'héritage du prototype de $scope a quelques inconvénients majeurs (c'est probablement la raison pour laquelle il serat supprimé d'Angular 2):
      • Il est difficile de suivre la trace des données pour savoir où elles vont.
      • Le changement de valeur d'un scope peut affecter certaines portées de façon inattendue.
      • Plus difficile à réusiner (refactoring).
      • La règle des points : 'dot rule(in English)'.
    • Ne pas utiliser $scope sauf pour le besoin d'opérations spéciales (comme $scope.$broadcast) est une bonne preparation pour AngularJS V2.
    • La syntaxe est plus proche du constructeur Javascript brut ('vanilla').

    Decouvrez la syntaxe controller as en détail: adoptez-la-syntaxe-controller-as

  • Eviter d'écrire la logique métier dans le contrôleur. Déplacez la logique métier dans un modèle, grâce à un service. Par exemple:

    //Ceci est un comportement répandu (mauvais exemple) d'utiliser le contrôleur pour implémenter la logique métier.
    angular.module('Store', [])
    .controller('OrderCtrl', function ($scope) {
    
      $scope.items = [];
    
      $scope.addToOrder = function (item) {
        $scope.items.push(item);//-->Logique métier dans le contrôleur
      };
    
      $scope.removeFromOrder = function (item) {
        $scope.items.splice($scope.items.indexOf(item), 1);//-->Logique métier dans le contrôleur
      };
    
      $scope.totalPrice = function () {
        return $scope.items.reduce(function (memo, item) {
          return memo + (item.qty * item.price);//-->Logique métier dans le contrôleur
        }, 0);
      };
    });

    Quand on utilise un service comme 'modèle' pour implémenter la logique métier, voici à quoi ressemble le contrôleur (voir 'utilisez un service comme votre Modèle' pour une implémentation d'un service-modèle):

    //Order est utilisé comme un 'modèle'
    angular.module('Store', [])
    .controller('OrderCtrl', function (Order) {
    
      $scope.items = Order.items;
    
      $scope.addToOrder = function (item) {
        Order.addToOrder(item);
      };
    
      $scope.removeFromOrder = function (item) {
        Order.removeFromOrder(item);
      };
    
      $scope.totalPrice = function () {
        return Order.total();
      };
    });

    Pourquoi la mise en place de la logique métier dans le contrôleur est une mauvaise pratique ?

    • Les contrôleurs sont instanciés pour chaque vue HTML et sont détruits au déchargement de la vue.
    • Les contrôleurs ne sont pas ré-utilisables - ils sont liés à la vue HTML.
    • Les contrôleurs ne sont pas destinés à êtres injectés.
  • Dans le cas de contrôleurs imbriqués utilisez les portées emboitées (avec controllerAs):

app.js

module.config(function ($routeProvider) {
  $routeProvider
    .when('/route', {
      templateUrl: 'partials/template.html',
      controller: 'HomeCtrl',
      controllerAs: 'home'
    });
});

HomeCtrl

function HomeCtrl() {
  this.bindingValue = 42;
}

template.html

<div ng-bind="home.bindingValue"></div>

Directives

  • Nommez vos directives en lowerCamelCase
  • Utilisez scope au lieu de $scope dans votre fonction de lien. Dans la compilation, les fonctions de liaison pré/post compilation, vous avez déjà les arguments qui sont passés lorsque la fonction est appelée, vous ne serez pas en mesure de les modifier à l'aide de DI. Ce style est également utilisé dans le code source d'AngularJS.
  • Utilisez les préfixes personnalisés pour vos directives pour éviter les collisions de noms de bibliothèques tierces.
  • Ne pas utiliser ng​​ ou ui comme préfixe car ils sont réservés pour AngularJS et l'utilisation d'AngularJS UI.
  • Les manipulations du DOM doivent être effectués uniquement avec des directives.
  • Créer un scope isolé lorsque vous développez des composants réutilisables.
  • Utilisez des directives comme des attributs ou des éléments au lieu de commentaires ou de classes, cela va rendre le code plus lisible.
  • Utilisez $scope.$on('$destroy, fn) pour le nettoyage de vos objects/variables. Ceci est particulièrement utile lorsque vous utilisez des plugins tiers comme directives.
  • Ne pas oublier d'utiliser $sce lorsque vous devez faire face à un contenu non approuvé.

Filtres

  • Nommez vos filtres en lowerCamelCase.
  • Faites vos filtres aussi légers que possible. Ils sont souvent appelés lors de la boucle $digest, donc créer un filtre lent ralentira votre application.
  • Limitez vos filtres à une seule chose et gardez-les cohérents. Des manipulations plus complexes peuvent être obtenues en enchaînant des filtres existants.

Services

La présente section contient des informations au sujet des composants service dans AngularJS. Sauf mention contraire, elles ne dépendent pas de la méthode utilisée pour définir les services (c.-à-d. provider, factory, service).

  • Nommez vos services en camelCase:
    • UpperCamelCase (PascalCase) pour vos services utilisés comme constructeurs, c.-à.-d.:
module.controller('MainCtrl', function ($scope, User) {
  $scope.user = new User('foo', 42);
});

module.factory('User', function () {
  return function User(name, age) {
    this.name = name;
    this.age = age;
  };
});
  • lowerCamel pour tous les autres services.

  • Encapsulez la logique métier dans des services.

  • La méthode service est préférable à la méthode factory. De cette façon, nous pouvons profiter de l'héritage classique plus facilement:

function Human() {
  //body
}
Human.prototype.talk = function () {
  return "I'm talking";
};

function Developer() {
  //body
}
Developer.prototype = Object.create(Human.prototype);
Developer.prototype.code = function () {
  return "I'm codding";
};

myModule.service('Human', Human);
myModule.service('Developer', Developer);
  • Pour un cache de session, vous pouvez utiliser $cacheFactory. Il devrait être utilisé pour mettre en cache les résultats des requêtes ou des calculs lourds.
  • Si un service donné nécessite une configuration, définissez le service comme un provider et configurez-le ainsi dans la fonction de rappel config:
angular.module('demo', [])
.config(function ($provide) {
  $provide.provider('sample', function () {
    var foo = 42;
    return {
      setFoo: function (f) {
        foo = f;
      },
      $get: function () {
        return {
          foo: foo
        };
      }
    };
  });
});

var demo = angular.module('demo');

demo.config(function (sampleProvider) {
  sampleProvider.setFoo(41);
});

Gabarits

  • Utilisez ng-bind ou ng-cloak au lieu de simples {{ }} pour prévenir les collisions de contenus
  • Eviter d'écrire du code complexe dans les gabarits
  • Quand vous avez besoin de définir le src d'une image dynamiquement, utilisez ng-src au lieu de src avec {{}} dans le gabarit. Ceci pour permettre un refresh dynamique ? (NLDT)
  • Au lieu d'utiliser la variable $scope en tant que chaîne et de l'utiliser avec l'atribut  style et {{}}, utilisez la directive ng-style avec les paramètres de l'objet comme et les variables de scope comme valeurs:
<script>
...
$scope.divStyle = {
  width: 200,
  position: 'relative'
};
...
</script>

<div ng-style="divStyle">my beautifully styled div which will work in IE</div>;

Routage

  • Utilisez resolve pour résoudre les dépendances avant que la vue ne soit affichée.
  • Ne placez pas d'appels REST à l'intérieur du callback resolve. Isolez les requêtes à l'intérieur de services appropriés. De cette manière, vous pourrez activer la mise en cache et appliquer le principe de séparation des problèmes (separation of concerns).

Tests E2E

Les tests E2E sont la prochaine étape logique après les tests unitaires. Cette étape permet de retracer les bugs et les erreurs dans le comportement de votre système. Ils confirment que les scénarios les plus communs de l'utilisation de votre application sont fonctionnels. De cette manière, vous pouvez automatiser le processus et l'exécuter à chaque fois que vous déployez votre application.

Idéallement, les tests E2E Angular sont écris avec Jasmine. Ces tests sont exécutés en utilisant l'exécuteur de tests Protractor E2E qui utilise des évènements natifs et qui possèdes des fonctionnalités spécifiques aux applications Angular.

Arborescence:

.
├── app
│   ├── app.js
│   ├── home
│   │   ├── home.html
│   │   ├── controllers
│   │   │   ├── FirstCtrl.js
│   │   │   ├── FirstCtrl.spec.js
│   │   ├── directives
│   │   │   └── directive1.js
│   │   │   └── directive1.spec.js
│   │   ├── filters
│   │   │   ├── filter1.js
│   │   │   └── filter1.spec.js
│   │   └── services
│   │       ├── service1.js
│   │       └── service1.spec.js
│   └── about
│       ├── about.html
│       ├── controllers
│       │   └── ThirdCtrl.js
│       │   └── ThirdCtrl.spec.js
│       └── directives
│           ├── directive2.js
│           └── directive2.spec.js
├── partials
├── lib
└── e2e-tests
    ├── protractor.conf.js
    └── specs
        ├── home.js
        └── about.js

i18n

  • Pour les versions les plus récentes du framework (>=1.4.0), utilisez les outils i18n intégrés. Lorsque vous utilisez de versions antérieures(<1.4.0), utilisez angular-translate.

Performance

  • Surveiller seulement les variables les plus importantes (par exemple, lors de l'utilisation de communication en temps réel, ne pas provoquer une boucle $digest dans chaque message reçu).
  • Pour un contenu initialisé une seule fois et qui ensuite ne change pas, utiliser des observateurs à évaluation unique comme bindonce.
  • Faire les calculs dans $watch aussi simples que possible. Faire des calculs lourds et lents dans un unique $watch va ralentir l'ensemble de l'application (la boucle $digest s'exécute dans un seul thread en raison de la nature mono-thread de JavaScript).
  • Mettre le troisième paramètre de la fonction $timeout à false pour éviter la boucle $digest lorsqu'aucune des variables observées n'est impactée par la fonction de rappel $timeout.

Contribution

Puisque ce guide de style a pour but d'être un projet communautaire, les contributions sont très appréciées. Par exemple, vous pouvez contribuer en développant la section Tests ou en traduisant le guide dans votre langue.