Créer et publier un module Angular

Publié le

Nous avons traduit pour vous l’article Medium de notre formateur : comment créer et publier un module Angular ?

Note : il y a désormais des outils pour vous aider dans la création d’une librairie Angular. Pour des informations à jour, consultez plutôt la version anglaise de cet article.

Lors d’un précédent article, je vous parlais du module que j’ai créé : angular-async-local-storage. Ce fut assez simple de créer un module Angular et de l’utiliser directement dans mon application. Mais comme cela pouvait aider d’autres développeurs, je voulais le transformer en module réutilisable comme n’importe quel autre module Angular.

Cette étape fut assez laborieuse. Je n’ai trouvé quasiment aucune documentation sur le sujet, alors j’ai tenté de copier le fonctionnement du module Http officiel. Maintenant que j’y suis parvenu, je tenais à partager mon expérience sur comment créer et publier un module Angular.

Cet article s’adresse à des développeurs/ses expérimenté/e/s, qui connaissent déjà les concepts principaux d’Angular et qui savent construire une application basique.

Créer un module Angular : les pièges

Cette partie est presque identique à la création d’un module dans votre application : importez les modules dont vous avez besoin, déclarez vos composants, directives et pipes, ou fournissez des services. Il y a seulement quelques détails auxquels il faut faire attention.

D’abord, n’importez jamais BrowerModule. Votre module est un feature module (un sous-module), seul l’utilisateur final devra importer BrowserModule dans le module root (principal) de son application. Si vous avez besoin des directives courantes (*ngIf, *ngFor…), importez CommonModule.

Si votre module consiste à créer de nouveaux composants, directives ou pipes, n’oubliez pas de les exporter. Se limiter à les déclarer les rend utilisables seulement à l’intérieur de ce module, pas à l’extérieur.

Plus important, évitez de mélanger des composants/directives/pipes et des services dans le même module. Pourquoi ?

  • Un service fourni dans un module sera utilisable partout dans l’application. Il faudra ainsi importer le module une seule fois, dans le module principal (comme le HttpModule).
  • Un composant/directive/pipe exporté sera utilisable seulement dans le module qui importe le vôtre. L’utilisateur devra donc réimporter votre module dans tous les modules (le principal et les sous-modules) qui en ont besoin (comme le CommonModule).

Si cela n’est pas clair pour vous, lisez notre article « Comprendre les modules Angular (NgModule) ». C’est un point important (et qui porte à confusion) dans Angular.

Enfin, respectez la règle d’or d’Angular : n’utilisez jamais directement des APIs spécifiques au navigateur (comme le DOM). Si vous le faites, votre module ne sera pas compatible avec la génération côté serveur (Universal) and d’autres options avancées d’Angular. Si vous n’avez pas le choix (par exemple pour le localStorage), vous devez intercepter les erreurs avec des try/catch.

Exporter l’API publique

Quand vous utilisez un module Angular officiel, vous accédez à un unique point d’entrée pour importer tout ce dont vous avez besoin (comme '@angular/http').

Vous devez donc créer un fichier index.ts, qui exporte toute l’API publique de votre module. Il devrait au minimum contenir votre NgModule, et vos composants/directives/pipes et services (l’utilisateur aura besoin de les importer pour les injecter là où il en a besoin).

Les composants/directives/pipes ne seront pas importés directement par l’utilisateur, mais vous devez les exporter pour être compatible avec la pré-compilation AoT.

Build tools

C’est la partie qui a été la plus difficile. J’ai donc pris modèle sur un module Angular officiel. Sont utilisés :

Configuration TypeScript

Voici le tsconfig.json de mon module :

Il y a des différences importantes avec votre tsconfig.json habituel :

  • des chemins explicites ("paths") sont nécessaires, car le package final ne les inclura pas directement (plus de précisions à ce sujet ci-dessous) ;
  • "angularCompilerOptions": { "strictMetadataEmit": true } est nécessaire pour être compatible avec la pré-compilation AoT ;
  • "declaration": true est important pour générer des fichiers de définitions, qui permettront à l’utilisateur d’avoir l’auto-complétion pour votre module ;
  • "noImplicitAny": true et "strictNullChecks": true sont recommandés pour réduire le risque d’erreurs et être compatible avec toutes les configurations utilisateur ; "noImplicitAny": true doit être respecté depuis Angular 4.0, et "strictNullChecks": true à partir d’Angular 4.1.
  • "module": "es2015" est important pour les performances, et "sourceMap": true pour le debug, mais rien de spécifique ici ;
  • "stripInternal": true évite du code inutile venant d’APIs internes et "skipLibCheck": true évite de voir la compilation bloquée par des erreurs (bénignes) venant des librairies que vous utilisez.

Configuration Rollup

Les modules Angular sont fournis au format UMD. Votre rollup.config.js doit donc être configuré en conséquence. Voici un exemple :

Le script en entrée ("entry") est votre fichier index.ts transpilé, cela doit donc correspondre à votre configuration TypeScript. bundles/modulename.umd.js est le chemin et le nom conventionnels utilisés par les modules Angular officiels.

Rollup nécessite un moduleName pour le format UMD. Cela sera un objet JavaScript, n’utilisez donc pas de caractères spéciaux (notamment les tirets).

Il s’agit ensuite d’un point central : votre module utilise des fonctionnalités d’Angular (au minimum le décorateur NgModule), mais votre package ne doit pas inclure Angular.

Pourquoi ? Angular sera déjà inclus dans l’application de l’utilisateur. Si votre module l’inclus aussi, il sera chargé deux fois, provoquant une erreur fatale (et incompréhensible).

Vous devez donc définir Angular en tant que global. Et pour cela, vous devez connaître le nom UMD de chaque module. Cela suit cette convention : ng.modulename (ng.core, ng.common, ng.http…).

Il en est de même pour RxJS, si votre module l’utilise. Les noms UMD sont plus complexes. Pour les classes (Observable…), c’est Rx. Pour les opérateurs (map, filter…), c’est Rx.Observable.prototype. Pour les méthodes directes de classes (of, fromEvent…), c’est Rx.ClassName (par exemple Rx.Observable).

Enfin, la compilation

Vous pouvez maintenant compiler votre module. Vous pouvez enregistrer les lignes de commandes dans des scripts npm :

Ensuite :

Notez que la transpilation n’est pas faite directement par TypeScript, vous devez utiliser le compileur Angular (ngc). Il s’agit de TypeScript, saupoudré d’un peu de magie Angular.

Publishing on npm

Ne publiez pas tout sur npm, seulement le répertoire dist.

Vous devez créer un fichier dist/package.json spécifique. Par exemple :

Quelques points importants :

  • vous devez suivre le semantic versioning. Toute cassure de rétro-compatibilité (même s’il s’agit d’un changement minime) signifie une incrémentation du numéro majeur (le premier : 2.0.0). Et quand vous modifierez votre module pour rester à jour avec Angular, il s’agira d’une incrémentation du numéro mineur (le second : 1.1.0) ;
  • les chemins "main" et "module" sont nécessaires pour les imports utilisateurs, et celui des "typings" pour l’auto-complétion ;
  • "licence": "MIT" : une licence open-source est importante, ou votre module sera inutilisable par les autres. Angular utilise la licence MIT, vous devriez vous y tenir ;
  • les modules Angular que vous avez utilisés doivent être listés dans les peerDependencies. Continuez à suivre le semver, avec le signe ^, ou votre module deviendra obsolète à chaque mise à jour d’Angular. Pour les librairies encore en beta (zone.js…), vous pouvez voir les pré-requis actuels d’Angular ici.

N’oubliez pas d’écrire un README, avec la documentation de votre API. Autrement, votre module ne sert à rien. Vous pouvez utilisez une librairie comme copyfiles pour copier votre README depuis votre répertoire racine (celui qui sera utilisé sur Github) vers le répertoire dist (qui sera utilisé sur npm).

Avec un compte npm configuré, vous pouvez maintenant publier votre module :

Et à chaque fois que vous mettrez à jour votre module, relancez la compilation, modifiez le numéro de version, mettez à jour le changelog et publiez à nouveau.

Découvrez notre formation Angular, animée par l’auteur de cet article !


10 réponses à “Créer et publier un module Angular”

  1. Super article,
    j’ai suivi le tuto, et tout fonctionne correctement,
    en revanche quand on a dans sa librairie, un composant avec un templateUrl qui vise un fichier .html du module,
    on a une erreur du type : « nhandled Promise rejection: Failed to load **.component.html ; Zone: ; Task: Promise.then ; Value: Failed to load **.component.html »,
    j’ai rajouté un script npm pour copier les fichiers html dans le dossier /dist car ngc ne s’occupe que des fichiers .ts. Mais rien y fait j’ai toujours la même erreur. Il ne trouve pas le fichier. Une idée ?

  2. Il faut que l’outil de packaging (rollup dans ce cas, ou webpack) soit configuré explicitement pour gérer les fichiers HTML (et CSS) externes.

    Une nouvelle version de cet article est prévue prochainement car avec les évolutions d’Angular 4 la procédure a un peu changé et il est sans doute possible de simplifier grâce à la pré-configuration du CLI.

  3. Merci pour le réponse. J’ai pu m’en sortir.
    Malheureusement, Angular-CLI ne prend toujours pas en compte la publication des librairies à l’heure actuelle.

  4. Salut,

    ça marche nickel, bravo & merci.

    En revanche, l’étape suivante serait de pouvoir utiliser ce module packagé dans le cadre d’un module lazy-loadé.
    ça permettrait de bien splitter son application et de rendre les développements des différents modules complètement indépendant. Chose indispensable si on veut par exemple faire un portail.

    merci de tout conseil…

  5. Salut,

    Merci beaucoup pour ce tutoriel.
    Lorsque j’ai fais mon build, ngc m’a demandé angular/core.
    Du coup, j’ai été obligé de l’ajouter au devDependencies de mon package.json (de même pour rxjs).

    Lorsque j’importe le module dans un autre projet, il m’oblige à utiliser angular/core@4.4.3
    +– UNMET PEER DEPENDENCY @angular/core@4.4.3

    Alors que j’ai bien mis comme toi dans les peerDependencies.
    « @angular/core »: « ^2.4.0 || ^4.0.0 »,

    Ais-je oublié de faire quelque chose?

    Merci

  6. Je retire se que j’ai dis.
    Ca marche.
    Mon problème n’a rien à voir avec ce que je pensais.

    Bonne journée 😀 et encore merci pour le tuto.

  7. Bonjour
    je bosse avec angular 5 et angular-cli 1.5.0
    J’ai suivi le tuto jusqu’à Exporter l’API publique
    par contre pour la suite j’ai quelques difficultés:
    je ne retrouve pas de fichier tsconfig.json pour typescript
    ni de fichier rollup.config.js

    Merci pour votre aide

  8. Bonjour,
    Merci beaucoup pour cet article !
    Je suis dans un problème similaire à @Jerems avec : Failed to load itr-simulation.component.html

    Pouvez-vous nous indiquer comment vous avez ajouté les templates html au package avec rollup ?

    Merci d’avance.

  9. Bonjour, solution simple : utiliser des templates inlines. Sinon, comme indiqué dans le commentaire précédent, référez-vous à l’article en Anglais pour des solutions plus avancées.

Laisser un commentaire

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

Ce site utilise Akismet pour réduire les indésirables. En savoir plus sur comment les données de vos commentaires sont utilisées.