Appearance
Créer une librairie Angular
Pourquoi créer une librairie ?
Imaginez une boîte à outils que vous pourriez partager avec vos collègues ou d'autres développeurs. C'est exactement ce qu'est une librairie Angular. Voici quelques raisons pour lesquelles vous pourriez vouloir créer une librairie :
Réutilisation du code : Au lieu de réécrire les mêmes composants ou services dans chaque projet, vous pouvez les centraliser dans une librairie et les réutiliser facilement.
Maintenance simplifiée : Les mises à jour et les corrections de bugs peuvent être effectuées à un seul endroit, puis propagées à tous les projets utilisant la librairie.
Cohérence : Une librairie permet d'assurer une cohérence visuelle et fonctionnelle entre différents projets, particulièrement utile pour les grandes organisations.
Collaboration : Les librairies facilitent le partage de code entre équipes ou avec la communauté open-source.
Modularité : Vous pouvez diviser votre application en modules plus petits et plus gérables, chacun pouvant être développé et testé indépendamment.
Optimisation des performances : Les librairies bien conçues peuvent être optimisées pour la taille du bundle et les performances, bénéficiant à tous les projets qui les utilisent.
EXEMPLE CONCRET
Imaginons que vous travaillez sur plusieurs projets Angular qui nécessitent tous une gestion des utilisateurs. Au lieu de recréer les composants de carte utilisateur, de liste d'utilisateurs, et les services associés dans chaque projet, vous pourriez les regrouper dans une librairie my-user-lib
. Cette approche vous fera gagner du temps, réduira les duplications de code et assurera une expérience utilisateur cohérente à travers vos applications.
Création de la librairie
1. Générer la librairie
Commençons par créer une nouvelle librairie avec la CLI Angular :
bash
ng new my-workspace --no-create-application
cd my-workspace
ng generate library my-user-lib
Nom de la librairie
Choisissez un nom explicite pour votre librairie qui décrit bien sa fonction. D'après la documentation officielle, le nom de la librairie devrait éviter de commencer par ng-
car c'est un préfixe réservé pour les librairies officielles d'Angular.
Ajouter une application
Si vous souhaitez créer une application qui utilise votre librairie, vous pouvez en ajouter une avec la commande ng generate application my-app
.
2. Structure du projet
Après la génération, vous aurez une structure comme celle-ci :
my-workspace/
├── projects/
│ └── my-user-lib/
│ ├── src/
│ │ ├── lib/
│ │ ├── public-api.ts
│ │ └── test.ts
│ ├── ng-package.json
│ ├── package.json
│ └── tsconfig.lib.json
└── package.json
3. Création d'un composant
Créons un composant réutilisable pour notre librairie :
ts
import { Component, Input } from '@angular/core';
import { CommonModule } from '@angular/common';
import { User } from './user.interface';
@Component({
selector: 'lib-user-card',
standalone: true,
imports: [CommonModule],
template: `
@if (user) {
<div class="user-card">
<h2>{{ user.name }}</h2>
<p>{{ user.email }}</p>
</div>
}
`,
styles: `
.user-card {
padding: 1rem;
border: 1px solid #ccc;
border-radius: 4px;
}
`
})
export class UserCardComponent {
@Input() user: User | undefined;
}
ts
export interface User {
id: number;
name: string;
username: string;
email: string;
}
4. Export des composants
Pour rendre votre composant accessible, vous devez l'exporter dans le public-api.ts
:
ts
export * from './lib/user-card/user-card.component';
export * from './lib/user-card/user.interface';
5. Ajouter des dépendances (optionnel)
Si votre librairie nécessite des dépendances externes, vous pouvez les spécifier dans le fichier package.json
de votre librairie. Voici un exemple de structure pour le package.json
(ici avec lodash
en dépendance) :
json
{
"name": "my-user-lib",
"version": "1.0.0",
"peerDependencies": {
"@angular/common": ">=17.0.0",
"@angular/core": ">=17.0.0"
},
"dependencies": {
"tslib": "^2.3.0",
"lodash": "^4.17.21"
},
"sideEffects": false,
"license": "MIT",
"author": "Votre Nom",
"repository": "https://github.com/votre-username/my-user-lib",
"publishConfig": {
"access": "public"
}
}
json
{
"$schema": "../../node_modules/ng-packagr/ng-package.schema.json",
"dest": "../../dist/my-user-lib",
"lib": {
"entryFile": "src/public-api.ts"
},
"allowedNonPeerDependencies": [
"lodash"
]
}
Expliquons chaque section :
- Les
peerDependencies
sont les dépendances que votre librairie s'attend à trouver dans le projet hôte. Typiquement, il s'agit des packages Angular core.
CONSEIL
Utilisez >=
pour la version d'Angular pour permettre la compatibilité avec les versions futures, tant que votre librairie reste compatible.
- Les
dependencies
sont les packages externes dont votre librairie a besoin pour fonctionner. Ces dépendances seront installées automatiquement avec votre librairie.
ATTENTION
Il faut penser à ajouter allowedNonPeerDependencies
dans le ng-package.json
si vous utilisez des dépendances qui ne sont pas des peerDependencies. De plus, si vous tester votre librairie en développement, il faut penser à ajouter la dépendance à la racine du projet avec npm install <nom-de-la-dependance>
.
6. Build et publication
Pour construire votre librairie :
bash
ng build my-user-lib
ATTENTION
Assurez-vous de mettre à jour la version dans le package.json
de votre librairie avant chaque publication.
Pour publier sur npm :
bash
cd dist/my-user-lib
npm publish
Utilisation de la librairie
Dans un projet Angular, vous pouvez maintenant installer et utiliser votre librairie :
bash
npm install my-user-lib
Tester en développement
Si vous souhaitez tester votre librairie en développement, vous n'êtes pas obligé de la publier sur npm. Vous pouvez directement l'utiliser dans votre code. Mais il faut penser à builder la librairie avant.
Vous pouvez builder en fond avec la commande ng build my-user-lib --watch
qui va reconstruire la librairie à chaque changement.
Exemple d'utilisation :
ts
import { Component } from '@angular/core';
import { UserCardComponent } from 'my-user-lib';
@Component({
selector: 'app-home',
standalone: true,
imports: [UserCardComponent],
template: `
<lib-user-card [user]="currentUser"></lib-user-card>
`
})
export class HomeComponent {
currentUser = {
id: 1,
name: 'John Doe',
username: 'johndoe',
email: '[email protected]'
};
}
Points d'entrée multiples
Lorsque votre librairie grandit, il peut être intéressant de la diviser en plusieurs points d'entrée (aussi appelés points d'entrée secondaires). Cette approche est notamment utilisée par Angular Material où chaque composant a son propre point d'entrée :
typescript
import { MatGridListModule } from '@angular/material/grid-list';
import { MatInputModule } from '@angular/material/input';
TIP
- Meilleur tree shaking : Le tree shaking est un processus qui élimine le code non utilisé. Par exemple, si vous n'utilisez que le composant UserCard, le code du UserList ne sera pas inclus dans votre application finale
- Optimisation du code splitting : Le code splitting permet de découper votre application en plusieurs fichiers qui seront chargés uniquement quand nécessaire. Par exemple, si un utilisateur ne va jamais sur la page de liste des utilisateurs, le code de cette fonctionnalité ne sera jamais téléchargé
- Gestion des dépendances plus flexible : Chaque point d'entrée peut avoir ses propres dépendances. Par exemple, le module de liste d'utilisateurs pourrait utiliser une librairie de tableau, tandis que le module de carte utilisateur pourrait utiliser une librairie de carte
- Meilleure séparation des fonctionnalités : Organisation plus claire du code, comme avoir tous les composants liés aux utilisateurs dans un même dossier
Création d'un point d'entrée secondaire
Prenons l'exemple d'un point d'entrée pour la gestion des utilisateurs. Voici la structure à créer :
my-workspace/
└── projects/
└── my-user-lib/
├── users/ # Nouveau point d'entrée
│ ├── src/
│ │ ├── user-list/
│ │ │ └── user-list.component.ts
│ │ └── user-card/
│ │ └── user-card.component.ts
│ ├── ng-package.json
│ └── public-api.ts
└── src/... # Point d'entrée principal
ts
import { Component } from '@angular/core';
import { CommonModule } from '@angular/common';
import { User } from '../../user.interface';
@Component({
selector: 'lib-user-list',
standalone: true,
imports: [CommonModule],
template: `
@for (user of users; track user.id) {
<div class="user-item">
{{ user.name }}
</div>
}
`
})
export class UserListComponent {
users: User[] = [];
}
ts
export * from './src/user-list/user-list.component';
export * from './src/user-card/user-card.component';
json
{
"$schema": "../../../../node_modules/ng-packagr/ng-package.schema.json",
"lib": {
"entryFile": "public-api.ts"
}
}
CONSEIL
Choisissez une structure logique pour vos points d'entrée. Par exemple, vous pouvez regrouper par :
- Fonctionnalité (auth, users, admin...)
- Type de composant (forms, layout, utils...)
- Domaine métier (billing, shipping, inventory...)
Utilisation des points d'entrée
Une fois configuré, vous pouvez importer spécifiquement ce dont vous avez besoin :
typescript
// Import depuis le point d'entrée principal
import { UserService } from 'my-user-lib';
// Import depuis le point d'entrée secondaire
import { UserListComponent } from 'my-user-lib/users';
Dépendances entre points d'entrée
Les points d'entrée peuvent dépendre les uns des autres. Par exemple, si vous avez un point d'entrée core
avec des services partagés :
ts
export * from './src/services/user.service';
ts
import { UserService } from 'my-user-lib/core';
@Component({
// ...
})
export class UserListComponent {
constructor(private userService: UserService) {}
}
ATTENTION
Évitez les dépendances circulaires entre les points d'entrée. ng-packagr détectera ces problèmes et générera une erreur.
Configuration TypeScript
Pour que l'IDE reconnaisse correctement les imports, ajoutez les chemins dans le tsconfig.json
à la racine du projet :
json
{
"compilerOptions": {
"paths": {
"my-user-lib": [
"./projects/my-user-lib"
],
"my-user-lib/*": [
"./projects/my-user-lib/*"
]
}
}
}