Intermédiaire

Comment bien organiser son projet Angular : Structure

puzzle_illustration_02Aujourd’hui, dans la plupart des cas, lorsque vous commencez un projet Angular (1.x) vous pensez à une architecture MVC. Il y a beaucoup de manières de coder en Angular, mais il y en a une particulièrement qui est, selon moi, plus claire. Dans cet article, je vais vous montrer comment monter un projet modulaire, avec de bonnes pratiques, facile à maintenir et à comprendre, puis nous verrons quels sont les avantages.

ATTENTION : Cet article n’a pas la prétention de vous montrer LA bonne voie à suivre pour coder en Angular, il y a autant de manière de développer que de développeurs.

Organisation des fichiers

Le pattern MVC

source
———/assets
——————/favicon
——————/fonts
——————/images
———/bower_components
——————/angular
——————/angular-routes
———/controllers
——————/home.controller.js
——————/articles-list.controller.js
——————/article-detail.controller.js
———/directives
——————/article
—————————/article.directive.js
—————————/_article.directive.html
———/filters
——————/translate.filter.js
———/partials
——————/_footer.html
——————/_header.html
——————/_home.html
———/resources
——————/locale-fr_FR.json
———/sass
——————/_directives.scss
——————/_fonts.scss
——————/_global.scss
——————/screen.scss
———/services
——————/locale.service.js
——————/article.service.js
———/app.config.js
———/app.module.js
———/app.routes.js
———/index.html

Voici un model MVC comme on les “aime”… Les contrôleurs d’un côté, les vues de l’autre, etc. Logiquement c’est ce vers quoi on va se tourner et c’est aussi la logique que j’ai adopté à mes débuts. Le principal problème que je vois ici est lorsque j’ai envie d’ajouter ou de modifier une nouvelle fonctionnalité dans une page, il est nécessaire que j’aille chercher le contrôleur, le service, la directive, le template, etc. Cela devient rapidement plus compliqué lorsqu’on a une application avec plus de 10 contrôleurs. AngularJS nous permet de gérer nos projets d’une manière plus simple et claire.

Une organisation orientée components

source
———/assets
——————/images
——————/favicon
——————/fonts
———/bower_components
——————/angular
——————/angula-routes
———/components  // Les composants qui composent la logique de notre application (controlleurs, routes, partials...)
——————/home
—————————/_home.html
—————————/_home.scss
—————————/home.controller.js
—————————/home.routes.js
——————/articles
—————————/article-list.routes.js
—————————/list
————————————/_articles-list.html
————————————/_articles-list.scss
————————————/articles-list.controller.js
—————————/detail
————————————/_article-detail.html
————————————/_article-detail.scss
————————————/article-detail.controller.js
———/modules // Tous les composants de notre application qui sont réutilisables même en dehors de notre application
——————/translate
—————————/locale.service.js
—————————/translate.module.js
—————————/translate.filter.js
———/resources
——————/locale-fr_FR.json
———/sass
——————/_fonts.scss
——————/_global.scss
——————/screen.scss
———/services
——————/article.service.js
———/shared // Tous les composants de notre application qui sont réutilisables et propres à notre application
——————/article
—————————/_article.directive.html
—————————/_article.directive.scss
—————————/article.directive.js
——————/_footer.html
——————/_head.html
——————/main.controller.js
———/app.config.js
———/app.module.js
———/index.html

Vous pouvez récupérer ce projet sur git : https://github.com/SoatGroup/angular-organisation

Il est vrai que de prime abord, cette structure peut paraitre compliquée, mais ce n’est qu’une question d’habitude. Il faut imaginer notre application Angular comme plusieurs petites applications MVC, cela nous permet une meilleure modularité et lisibilité (après adaptation). Nous ne classons plus nos fichiers par rapport à leur catégorie mais par rapport au sujet auquel ils appartiennent.

Ici, comme vous pouvez le voir les composant sont individuel, par exemple si je décide de supprimer le dossier app/components/articles, cela ne posera aucun problème. Un développeur va plus facilement s’y retrouver afin de modifier ou d’ajouter une fonctionnalité dans un fichier ou dans plusieurs qui sont lié entre eux. D’un simple coup d’oeil on est capable d’identifier le contenu. De plus, on se rapproche d’une organisation typique à Angular 2, plus simple si on veut franchir le cap.

Components :

Ce qu’on appelle “component” ici va correspondre aux différentes parties de notre application (article de blog, page d’accueil, liste de résultats, etc.), il va comprendre un controller, une vue, un scss et une route. Comme dit plus haut, chaque component est individuel, en gros il ne dépend pas des autres et fonctionne comme une petite application MVC.

Dans le cas du component “articles”, on peut constater qu’il y deux sous dossiers : “list” et “detail”. En effet, dans le cas où il y a plusieurs sous sections il est préférable de faire des sous dossiers, toujours pour une question de lisibilité.

Modules :

Le dossier “modules” va contenir tous les modules externes à l’application ou tous les modules qu’on peut réutiliser dans une autre application. Ici par exemple, j’ai développé un petit module de traduction, celui-ci pourra être utilisé à peu près n’importe où. Ce qui n’est pas du tout le cas dans notre première structure en MVC.

Resources :

Nous allons mettre ici toutes nos données statiques, par example les fichiers de langues.

Services :

J’ai préféré mettre mes services dans un dossier à part car il m’est souvent arrivé d’utiliser certains services à plusieurs endroits, mais vous êtes libres de les mettre avec vos components s’ils leurs sont propres.

Shared :

Dans “shared” nous allons mettre toutes nos directives, les vues qui apparaissent à plusieurs endroits dans l’application, nos filtres, etc. Avez-vous remarqué comment sont organisées les directives ? Cela peut vous sembler proche de ce qui est fait avec les components, c’est effectivement le cas car nous sommes dans une démarche de nous rapprocher d’une structure type d’AngularJS 2. (Pour info : Nous pourrions ne pas utiliser de directive, je vous laisse lire la doc)

Encapsulation

Afin d’éviter les conflits de scope, variables, fonctions… Et surtout pour rester modulaire, il est conseillé d’encapsuler vos composants dans une fonction anonyme :

(function() {
    // Mon composant…
})();

Modules

Il faut éviter de déclarer les modules en les affectant à une variable :

var app = angular.module(‘MonModule’, [‘AutreModule’]);

Pourquoi ? Parce que dans une architecture modulaire cela n’a pas lieu d’être. De plus, souvenez-vous, nous encapsulons tous nos composants dans une fonction anonyme qui ont leur propre scope, du coup impossible d’accéder à la variable app partout… Et c’est tant mieux :). Donc le mieux reste :

angular
    .module('Blog', [
        'Translate'
    ]);

Déclaration des composants

Que ce soit pour une directive, un controller ou un service, il est recommandé de les déclarer avec une fonction nommée plutôt qu’anonyme.

Pas bon :

angular
    .module(‘Blog’)
    .controller(‘Home’, function() {
        // …
    }]);

Bon :

angular
    .module(‘Blog’)
    .controller(‘HomeController’, [HomeController]);

///////////////

function HomeController() {
    // …
}

C’est quand même beaucoup plus lisible non ?

ControllerAs

Je ne vais pas vous énumérer les avantages d’utiliser le controllerAs plutôt que le $scope, beaucoup d’articles en parlent, je vais juste vous montrer une syntaxe plus claire.

angular
    .module(‘Blog’)
    .controller(‘HomeController’, [HomeController]);

///////////////

function HomeController() {
    var vm = this;

    // Attributs

    vm.title = ‘Bienvenue sur mon blog’;
    vm.news = [];

    // Méthodes

    vm.more = more;

    ///////////////

    // Définition des méthodes

    function more(key) {
        vm.news[key].more = ! vm.news[key].more;
    }
}

Même principe pour les directives, les services etc.

Les routes

Comme vous l’avez vu dans mon organisation, il y a un fichier routes par composant, cela permet une meilleure modularité et d’y ajouter un resolver… Prenons notre exemple d’articles, afin d’éviter les chargements asynchrones dans le controller après le chargement de la route ou afin de gérer les erreurs avant même d’atterrir dans le controller, c’est mieux de charger les données au moment de router :

angular
    .module('Blog')
    .config(['$routeProvider', articlesRoutes]);

///////////////

function articlesRoutes($routeProvider) {
    $routeProvider
        .when('/article/:idArticle', {
            controller: 'ArticleDetailController',
            controllerAs: 'articleVm',
            templateUrl: 'partials/_article-detail.html',
            resolve: {
                data: ['$q', 'article', getDataArticleDetai]
            }
        });

    ///////////////

    function getDataArticleDetai($q, article) {
        var defer = $q.defer();

        resolve();

        return defer.promise;

        ///////////////

        function resolve() {
            article
                .get()
                .then(fetchArticle, handeError);

            ///////////////

            function fetchMenu(data) {
                defer.resolve(data);
            }

            function handeError(data) {
                defer.reject(data);
            }
        }
    }
}

Les grosses applications

Dans le cas où votre application a du succès, elle a besoin de grosses évolutions et vous êtes en patern MVC. Votre dossier controllers contient 28 fichiers, vous n’osez même pas compter le nombre de vues et votre fichier scss contient 2000 lignes… Pas très pratique tout ça, ça donne envie de repartir from scratch. Dans le cas d’une petite application comme la nôtre, une structure MVC ne pose pas de problème, mais mieux vaut s’habituer à avoir de bonnes pratiques dès le départ.

La structure que je vous ai présentée est très bien adaptée pour de grosses applications. Elle vous permet une meilleure lisibilité et maintenabilité sur vos différents composants. Ajouter ou supprimer une fonctionnalité n’a jamais été aussi simple, presque aussi simple que de supprimer ou d’ajouter un dossier.

Prenez aussi l’habitude de nommer vos fichiers de manière claire (si ce n’est pas déjà fait), par exemple pour un controller : detail.controller.js, un service : article.service.js, etc.

Dans un prochain article je parlerai de l’automatisation des taches avec Gulp pour ce type de projet, puis nous aborderons la phase de compilation, de minification et d’execution.

Conclusion

Il est très important de garder un code claire et facile à maintenir, surtout lorsqu’on travaille dans une équipe. Organiser ses projets de façon modulaire apporte une grande souplesse et une meilleure clarté, c’est ce qu’Angular nous propose. De plus, il est à noter que cette architecture vous permettra à terme de migrer plus facilement vers Angular 2 qui propose une architecture orientée composant.

Cette structure comme vu plus haut nous permettra de plus facilement se retrouver dans son code et donc de le maintenir ou le debugger plus simplement. Votre code sera plus simple à faire évoluer, ajouter un nouveau component, un nouveau module, une nouvelle directive, etc. n’interfèrera pas avec le reste de votre application (il est encore plus simple de supprimer une fonctionnalité). En ce qui concerne les tests, vous pourrez les intégrer dans chaque composant de manière plus claire.

© SOAT
Toute reproduction interdite sans autorisation de la société SOAT

Nombre de vue : 3085

COMMENTAIRES 4 commentaires

  1. Sin dit :

    Bonjour, vous n’abordez pas du tout la question du namespacing pour éviter les collisions, un avis sur le sujet ?

  2. Nicolas GARIN dit :

    Bonjour,

    A ma connaissance, il n’y a pas de système de namespacing en JS et je n’ai jamais eu a m’en soucier. L’une des solutions qui pourrait éviter les collisions serait de créer un module pour chaque component. Le Hic c’est qu’on perd en automatisme car il faudra déclarer chaque module créé dans le module principal.

    Voilà un article qui en parle : https://www.safaribooksonline.com/blog/2014/03/27/13-step-guide-angularjs-modularization/ (je ne suis pas très fan de cette structure mais ce n’est que mon avis)

  3. […] nous avons notre projet Angular avec notre belle organisation. Cependant, vous noterez qu’il nous est impossible de le lancer en l’état. Dans cet article […]

  4. Mick dit :

    Bonjour , je ne comprend pas comment l’injection de dépendance fonctionne , car les controllers se trouvent dans components/XXX/

    exemple :

    /components // Les composants qui composent la logique de notre application (controlleurs, routes, partials…)
    ——————/home
    —————————/home.controller.js

    Comment déclarer mon controller dans app.js ??

    angular
    .module(‘Blog’)
    .controller(‘HomeController’, [HomeController]);

AJOUTER UN COMMENTAIRE