Maîtrisez l'organisation d'un projet Angular avec les ES Modules, barrel exports (index.ts), path aliasing tsconfig, détection des dépendances circulaires et module resolution.
ES Modules vs CommonJS en TypeScript
TypeScript peut compiler vers deux systemes de modules radicalement differents : ES Modules (ESM) et CommonJS (CJS). Comprendre leurs differences est fondamental pour eviter des bugs subtils au moment du bundling ou de l'execution Node.js.
CommonJS : le systeme historique de Node.js
CommonJS utilise require() et module.exports. Les imports sont resolus dynamiquement a l'execution, ce qui rend le tree-shaking difficile pour les bundlers.
// CommonJS — syntaxe historique Node.js
// Les imports sont resolus au moment de l'execution (dynamique)
const express = require('express');
const { readFileSync } = require('fs');
// Export d'un objet ou d'une valeur
module.exports = {
startServer: function() { /* ... */ }
};
ES Modules : le standard moderne
Les ES Modules utilisent import / export statiques. Les imports sont analyses a la compilation, ce qui permet le tree-shaking et l'analyse statique des dependances.
// ES Modules — syntaxe standard (TypeScript et navigateurs modernes)
// Les imports sont resolus statiquement (analyses a la compilation)
import { Injectable } from '@angular/core';
import type { User } from './models/user.model';
// Named export : on exporte une fonction nommee
export function formatDate(date: Date): string {
return date.toLocaleDateString('fr-FR');
}
// Default export : un seul par module (a eviter en Angular)
export default class AppService {
// ...
}
Que choisir en TypeScript / Angular ?
Angular utilise exclusivement les ES Modules. Le fichier tsconfig.json doit specifier le module cible via la propriete "module".
// tsconfig.json — configuration du systeme de modules
{
"compilerOptions": {
// "ESNext" preserve les import/export pour que le bundler les traite
// Ne jamais utiliser "CommonJS" dans un projet Angular
"module": "ESNext",
// "bundler" est le mode de resolution recommande avec Vite/esbuild
"moduleResolution": "bundler",
// Cible d'execution JavaScript (ES2022 recommande pour Angular 17+)
"target": "ES2022"
}
}
| Critere | CommonJS | ES Modules |
|---|---|---|
| Syntaxe | require() / module.exports |
import / export |
| Resolution | Dynamique (runtime) | Statique (compile time) |
| Tree-shaking | Difficile ou impossible | Natif et efficace |
| Top-level await | Non supporte | Supporte |
| Usage Angular | Non recommande | Standard obligatoire |
"module": "ESNext". CommonJS est reserve aux scripts Node.js legacy ou aux outils de build.
import type vs import : verbatimModuleSyntax
TypeScript 3.8 a introduit import type, et TypeScript 5.0 a renforce son usage avec l'option verbatimModuleSyntax. Cette distinction a un impact direct sur la taille du bundle et la compatibilite avec les outils de build.
La difference fondamentale
Un import classique peut emettre du code JavaScript a l'execution. Un import type est entierement efface par le compilateur TypeScript — il n'existe plus dans le JS produit.
// import classique — peut emettre du JS si utilise comme valeur
import { Component, OnInit } from '@angular/core';
import { User } from './models/user.model';
// import type — TOUJOURS efface, jamais emis en JS
// Utiliser quand on n'utilise l'import QUE comme type TypeScript
import type { UserProfile } from './models/user-profile.model';
import type { ApiResponse } from './interfaces/api.interface';
// Exemple concret : quand utiliser import type ?
import { Injectable } from '@angular/core';
import { HttpClient } from '@angular/common/http';
// UserProfile n'est utilise que comme annotation de type
// Le compilateur l'effacera automatiquement du JS produit
import type { UserProfile } from '../models/user-profile.model';
@Injectable({ providedIn: 'root' })
export class UserService {
constructor(private http: HttpClient) {}
// UserProfile apparait uniquement dans la signature de type
// Il sera efface a la compilation → pas d'import inutile en JS
getUser(id: number): Observable<UserProfile> {
return this.http.get<UserProfile>(`/api/users/${id}`);
}
}
verbatimModuleSyntax : le mode strict
Depuis TypeScript 5.0, l'option verbatimModuleSyntax force l'utilisation explicite de import type pour tous les imports qui ne servent que de types. Le compilateur genere une erreur si un import de valeur est utilise uniquement comme type.
// tsconfig.json — activation de verbatimModuleSyntax
{
"compilerOptions": {
// Force l'utilisation de "import type" pour les imports purement typages
// Recommande avec "module": "ESNext" et les bundlers modernes
"verbatimModuleSyntax": true,
"module": "ESNext",
"moduleResolution": "bundler"
}
}
// AVEC verbatimModuleSyntax: true
// ERREUR — UserProfile est un type, il faut "import type"
// import { UserProfile } from './models/user-profile.model'; // ❌
// CORRECT — import type explicite
import type { UserProfile } from './models/user-profile.model'; // ✅
// CORRECT — Component est utilise comme valeur (decorateur)
import { Component } from '@angular/core'; // ✅
verbatimModuleSyntax remplace les anciennes options importsNotUsedAsValues et preserveValueImports deprecies depuis TypeScript 5.0. Privilegiez la nouvelle option dans les nouveaux projets.
Import type inline : la syntaxe compacte
// Import type inline : melanger valeurs et types dans un seul import
// Utile quand on importe depuis le meme module
import { Component, type OnInit, type OnDestroy } from '@angular/core';
@Component({ selector: 'app-root', template: '' })
export class AppComponent implements OnInit, OnDestroy {
// OnInit et OnDestroy sont effaces ; Component reste dans le JS
ngOnInit(): void { /* ... */ }
ngOnDestroy(): void { /* ... */ }
}
Barrel exports : index.ts et tree-shaking
Un barrel export est un fichier index.ts qui re-exporte les membres d'un dossier depuis un point d'entree unique. C'est un pattern tres populaire dans les projets Angular, mais il comporte des pieges importants si mal utilise.
Principe du barrel export
// Structure du dossier src/app/core/models/
// ├── user.model.ts
// ├── product.model.ts
// ├── order.model.ts
// └── index.ts ← barrel export
// index.ts — re-exporte tout le contenu du dossier
// Chaque re-export rend les types publics depuis ce dossier
export { User, UserRole } from './user.model';
export { Product, ProductCategory } from './product.model';
export { Order, OrderStatus } from './order.model';
// Re-export de types uniquement (recommande avec verbatimModuleSyntax)
export type { UserProfile } from './user-profile.model';
// AVANT barrel — imports longs et repetitifs
import { User } from '../core/models/user.model';
import { Product } from '../core/models/product.model';
import { Order } from '../core/models/order.model';
// APRES barrel — import depuis le point d'entree unique
// Plus lisible, plus facile a maintenir
import { User, Product, Order } from '../core/models';
Avantages des barrel exports
- Imports plus courts et plus lisibles dans les fichiers consommateurs.
- API publique d'un module clairement definie dans un seul fichier.
- Refactoring facilite : deplacer un fichier sans changer les imports externes.
- Encapsulation : les fichiers non-exportes restent prives au dossier.
Inconvenients et impact sur le tree-shaking
Les barrels peuvent degrader les performances si utilises sans discernement. Un bundler comme esbuild ou Webpack doit analyser tout le barrel pour savoir ce qui est utilise, ce qui ralentit le build et peut inclure du code inutile.
// PROBLEME : barrel qui importe tout, y compris des modules lourds
// Si un seul consommateur importe "UserService",
// le bundler charge AUSSI HeavyChartModule si le barrel le re-exporte
// src/app/shared/index.ts (barrel trop large — a eviter)
export * from './components/button.component';
export * from './components/modal.component';
export * from './services/user.service';
// Attention : si HeavyChartModule importe des libs tierces volumineuses,
// tout consommateur du barrel les chargera potentiellement
export * from './charts/heavy-chart.module';
// SOLUTION : barrels granulaires et specifiques
// Separer les barrels par domaine fonctionnel
// src/app/shared/components/index.ts
export * from './button.component';
export * from './modal.component';
// Ne pas melanger composants UI et services lourds
// src/app/shared/services/index.ts
export * from './user.service';
export * from './auth.service';
// Chaque consommateur n'importe que ce dont il a besoin
import { ButtonComponent } from '@shared/components';
import { UserService } from '@shared/services';
export * vs export { } : quelle difference ?
// export * — re-exporte TOUT ce qui est exporte depuis le module source
// Simple mais peut exposer des membres non intentionnels
export * from './user.model';
// export { } nomme — re-exporte UNIQUEMENT les membres specifies
// Plus explicite, meilleure maitrise de l'API publique (recommande)
export { User, UserRole } from './user.model';
// export * as namespace — re-exporte sous un namespace
// Utile pour eviter les collisions de noms
export * as UserModels from './user.model';
Dependances circulaires : detection et prevention
Une dependance circulaire survient quand le module A importe B, et que B importe A (directement ou indirectement). Ce pattern provoque des valeurs undefined au runtime et des bugs difficiles a diagnostiquer.
Exemple de dependance circulaire
// PROBLEME : dependance circulaire entre deux services
// src/app/core/services/user.service.ts
import { Injectable } from '@angular/core';
// UserService importe OrderService
import { OrderService } from './order.service';
@Injectable({ providedIn: 'root' })
export class UserService {
constructor(private orderService: OrderService) {}
getUserOrders(userId: number) {
return this.orderService.getByUser(userId);
}
}
// src/app/core/services/order.service.ts
import { Injectable } from '@angular/core';
// OrderService importe UserService → CYCLE !
import { UserService } from './user.service';
@Injectable({ providedIn: 'root' })
export class OrderService {
constructor(private userService: UserService) {}
getByUser(userId: number) {
// Au moment de l'execution, l'un des deux peut etre "undefined"
return this.userService.getProfile(userId);
}
}
Detecter les cycles avec des outils
Deux outils sont particulierement efficaces pour detecter les dependances circulaires dans un projet Angular : madge et le plugin circular-dependency-plugin.
# Installation de madge (outil en ligne de commande)
npm install --save-dev madge
# Detecter toutes les dependances circulaires dans src/
npx madge --circular --extensions ts src/
# Generer un graphe visuel des dependances (format SVG)
# Necessite graphviz installe sur le systeme
npx madge --image dependency-graph.svg --extensions ts src/
// Pour un projet Angular avec esbuild, detecter les cycles
// via une configuration TypeScript stricte
// tsconfig.json — options qui aident a detecter les problemes
{
"compilerOptions": {
// Detecte les imports jamais utilises (symptome d'un mauvais design)
"noUnusedLocals": true,
"noUnusedParameters": true,
// Force la coherence entre les modules
"isolatedModules": true,
// Empêche l'utilisation de valeurs potentiellement undefined
"strictNullChecks": true
}
}
Strategies pour eliminer les cycles
La solution la plus efficace est d'extraire les types partages dans un module tiers sans dependances.
// SOLUTION 1 : extraire les types dans un module independant
// src/app/core/models/user.types.ts — module sans dependances
// Ce module ne depend d'aucun autre module metier
export interface UserSummary {
id: number;
name: string;
email: string;
}
// src/app/core/services/user.service.ts
import { Injectable } from '@angular/core';
// Plus d'import circulaire : on importe uniquement le type
import type { UserSummary } from '../models/user.types';
@Injectable({ providedIn: 'root' })
export class UserService {
// Retourne un type simple, pas OrderService
getProfile(userId: number): Observable<UserSummary> {
return this.http.get<UserSummary>(`/api/users/${userId}`);
}
}
// SOLUTION 2 : inverser la dependance via un token d'injection
// src/app/core/tokens/order-provider.token.ts
import { InjectionToken } from '@angular/core';
// Interface sans import circulaire : contrat entre les deux services
export interface IOrderProvider {
getByUser(userId: number): Observable<Order[]>;
}
// Token d'injection pour le service
export const ORDER_PROVIDER = new InjectionToken<IOrderProvider>('OrderProvider');
// user.service.ts — injecte le token, pas la classe concrete
import { Inject, Injectable, Optional } from '@angular/core';
import { ORDER_PROVIDER, IOrderProvider } from '../tokens/order-provider.token';
@Injectable({ providedIn: 'root' })
export class UserService {
constructor(
@Optional() @Inject(ORDER_PROVIDER) private orderProvider: IOrderProvider | null
) {}
}
Path aliasing avec tsconfig paths dans Angular
Les alias de chemins permettent de remplacer les chemins relatifs longs (../../../core/services) par des chemins semantiques (@core/services). C'est un outil puissant pour l'organisation d'un projet Angular a grande echelle.
Configuration dans tsconfig.json
// tsconfig.json — configuration des alias de chemins
{
"compilerOptions": {
// baseUrl est obligatoire pour utiliser les paths
// Il definit la racine a partir de laquelle les alias sont resolus
"baseUrl": ".",
// Alias de chemins : chaque cle est un pattern, la valeur un tableau de chemins
"paths": {
// @core/* → remplace "src/app/core/*"
"@core/*": ["src/app/core/*"],
// @shared/* → remplace "src/app/shared/*"
"@shared/*": ["src/app/shared/*"],
// @features/* → remplace "src/app/features/*"
"@features/*": ["src/app/features/*"],
// @env/* → remplace les fichiers d'environnement
"@env/*": ["src/environments/*"],
// Alias exact (sans wildcard) pour un fichier specifique
"@app-config": ["src/app/app.config"]
}
}
}
Utilisation dans le code Angular
// AVANT alias — chemins relatifs profonds et fragiles
// Si on deplace ce fichier, tous ces imports cassent
import { AuthService } from '../../../core/services/auth.service';
import { UserModel } from '../../../core/models/user.model';
import { ButtonComponent } from '../../shared/components/button/button.component';
import { environment } from '../../../../environments/environment';
// APRES alias — chemins absolus semantiques
// Ces imports fonctionnent quelle que soit la profondeur du fichier
import { AuthService } from '@core/services/auth.service';
import { UserModel } from '@core/models/user.model';
import { ButtonComponent } from '@shared/components/button/button.component';
import { environment } from '@env/environment';
Configurer Angular pour respecter les alias
Avec Angular CLI et esbuild (Angular 17+), les alias tsconfig paths sont automatiquement pris en charge. Pour les anciennes versions avec Webpack, une configuration supplementaire peut etre necessaire.
// angular.json — aucune configuration supplementaire necessaire avec esbuild
// Les alias tsconfig.paths sont resolus automatiquement par @angular-devkit
// VERIFICATION : s'assurer que tsconfig.app.json herite du tsconfig.json racine
// tsconfig.app.json
{
"extends": "./tsconfig.json",
"compilerOptions": {
// Peut surcharger certaines options specifiques a la compilation de l'app
"outDir": "./out-tsc/app",
"types": []
},
"files": [
"src/main.ts"
],
"include": [
"src/**/*.d.ts"
]
}
// tsconfig.spec.json herite aussi du tsconfig.json racine
// Les tests Jest/Karma ont donc acces aux memes alias de chemins
{
"extends": "./tsconfig.json",
"compilerOptions": {
"outDir": "./out-tsc/spec",
"types": ["jasmine"]
}
}
Alias avec Jest (configuration complementaire)
// jest.config.ts — configuration Jest pour resoudre les alias
// Jest ne lit pas tsconfig.json directement, il faut mapper les alias
import type { Config } from 'jest';
const config: Config = {
// Mapping des alias TypeScript vers les chemins reels
// La cle est une regex, la valeur un tableau de chemins
moduleNameMapper: {
// Resout @core/xxx vers src/app/core/xxx
'^@core/(.*)$': '/src/app/core/$1',
'^@shared/(.*)$': '/src/app/shared/$1',
'^@features/(.*)$': '/src/app/features/$1',
'^@env/(.*)$': '/src/environments/$1',
},
preset: 'jest-preset-angular',
};
export default config;
Module resolution : node16, bundler, classic
L'option moduleResolution dans tsconfig.json indique a TypeScript comment trouver les fichiers correspondant a un import. Ce choix a un impact direct sur la compatibilite avec Node.js, les bundlers et les packages tiers.
Les quatre strategies disponibles
| Valeur | Usage recommande | Compatibilite |
|---|---|---|
classic |
Projets tres anciens uniquement | TypeScript 1.x uniquement |
node (ou node10) |
Node.js CJS legacy | Node.js < 12, anciens projets |
node16 / nodenext |
Node.js ESM natif | Node.js 16+, ESM strict |
bundler |
Vite, esbuild, Webpack, Rollup | Angular 17+, projets modernes |
bundler : le choix optimal pour Angular
Depuis TypeScript 5.0, "moduleResolution": "bundler" est la valeur recommandee pour les projets utilisant un bundler moderne comme esbuild (Angular 17+). Elle autorise les imports sans extension tout en supportant les exports maps des packages npm.
// tsconfig.json — configuration optimale pour Angular 17+ avec esbuild
{
"compilerOptions": {
// bundler : mode concu pour les bundlers modernes (esbuild, Vite, Webpack 5)
// Autorise les imports sans extension (ex: import './utils' sans '.js')
// Supporte les "exports" maps dans package.json
"moduleResolution": "bundler",
// ESNext preserve les import/export pour le bundler
"module": "ESNext",
// Necessite module: ESNext ou module: Preserve
// Force l'usage de "import type" pour les types purs
"verbatimModuleSyntax": true,
// ES2022 cible les navigateurs modernes et Node.js 16+
"target": "ES2022"
}
}
node16 : quand l'utiliser ?
node16 est la bonne option pour les bibliotheques ou outils Node.js qui publient en ESM natif. Il impose des regles strictes : les imports doivent inclure l'extension .js et le champ type: "module" doit etre present dans package.json.
// package.json — requis pour node16 ESM
{
"name": "ma-librairie",
// Declare explicitement le module comme ESM
"type": "module",
"exports": {
// Point d'entree ESM
".": {
"import": "./dist/index.js",
"types": "./dist/index.d.ts"
}
}
}
// Avec node16, les imports doivent inclure l'extension .js
// meme si le fichier source est un .ts
// (TypeScript resout automatiquement .js → .ts)
import { MyFunction } from './utils.js'; // ✅ avec node16
// import { MyFunction } from './utils'; // ❌ invalide avec node16
nodenext est identique a node16 mais suit les evolutions futures de Node.js. Pour les nouvelles librairies Node.js ESM, privilegier nodenext.
Fichiers de declaration .d.ts et ambient modules
Les fichiers .d.ts sont des fichiers de declaration TypeScript : ils decrivent les types d'un module sans contenir de code executable. Ils permettent d'ajouter le typage TypeScript a des librairies JavaScript ou de declarer des modules speciaux.
Quand creer un fichier .d.ts ?
- Quand une librairie JavaScript tierce n'a pas de types (
@types/xxxinexistant). - Pour declarer des modules non-TypeScript (images, SVG, CSS Modules, JSON).
- Pour augmenter les types d'une librairie existante (declaration merging).
- Pour declarer des variables globales injectees par l'environnement (ex:
__ENV__,window.APP_CONFIG).
Ambient module : typer une librairie sans types
// src/types/legacy-lib.d.ts
// Declare un module JavaScript sans types disponibles
// "declare module" cree un module ambient TypeScript
declare module 'legacy-chart-lib' {
// Declare les exports du module avec leurs types
export interface ChartOptions {
type: 'bar' | 'line' | 'pie';
data: number[];
labels: string[];
colors?: string[];
}
// Declare la fonction principale de la librairie
export function createChart(
container: HTMLElement,
options: ChartOptions
): { destroy(): void; update(data: number[]): void };
}
// Wildcard ambient module — accepte tous les imports d'un pattern
// Utile pour les fichiers SVG, images, CSS Modules
declare module '*.svg' {
// Declare que les imports SVG retournent une URL string
const content: string;
export default content;
}
declare module '*.png' {
const content: string;
export default content;
}
Augmentation de module (declaration merging)
// src/types/express-augmentation.d.ts
// Augmente les types Express pour ajouter une propriete "user" sur Request
// C'est une technique avancee pour etendre les types d'une librairie tierce
import { User } from '../app/core/models/user.model';
// "declare module" avec le nom exact du module augmente
declare module 'express-serve-static-core' {
interface Request {
// Ajoute la propriete user au type Request d'Express
// Disponible sur toutes les routes apres le middleware d'authentification
user?: User;
}
}
Variables globales et environnement
// src/types/global.d.ts
// Declare des variables globales injectees par le bundler ou l'environnement
// Ces declarations doivent etre dans un fichier .d.ts inclus par tsconfig
// Variables injectees par esbuild ou Vite (ex: define dans vite.config.ts)
declare const __APP_VERSION__: string;
declare const __BUILD_DATE__: string;
declare const __IS_PRODUCTION__: boolean;
// Extension de l'objet Window pour des proprietes personnalisees
interface Window {
// Configuration injectee depuis le HTML ou le serveur
APP_CONFIG?: {
apiUrl: string;
featureFlags: Record<string, boolean>;
};
}
// src/types/environment.d.ts
// Declare l'interface des fichiers d'environnement Angular
export interface Environment {
production: boolean;
apiUrl: string;
wsUrl: string;
}
Inclure les .d.ts dans tsconfig
// tsconfig.json — s'assurer que les fichiers .d.ts sont inclus
{
"compilerOptions": {
// typeRoots : dossiers de declarations de types
// TypeScript cherche ici les declarations @types/xxx
"typeRoots": [
"./node_modules/@types",
"./src/types" // Nos declarations personnalisees
]
},
// include : fichiers sources TypeScript a compiler
// Les .d.ts dans src/types/ sont automatiquement inclus
"include": [
"src/**/*.ts",
"src/**/*.d.ts"
]
}
Structure d'un projet Angular avec alias de chemins
Une architecture Angular bien organisee avec des alias de chemins rend le code plus lisible, plus maintenable et plus facile a faire evoluer. Voici une structure eprouvee pour un projet de taille moyenne a grande.
Structure de dossiers recommandee
src/
├── app/
│ ├── core/ ← Alias : @core
│ │ ├── guards/
│ │ │ ├── auth.guard.ts
│ │ │ └── index.ts ← Barrel
│ │ ├── interceptors/
│ │ │ ├── auth.interceptor.ts
│ │ │ ├── error.interceptor.ts
│ │ │ └── index.ts ← Barrel
│ │ ├── models/
│ │ │ ├── user.model.ts
│ │ │ ├── api-response.model.ts
│ │ │ └── index.ts ← Barrel
│ │ ├── services/
│ │ │ ├── auth.service.ts
│ │ │ ├── user.service.ts
│ │ │ └── index.ts ← Barrel
│ │ └── index.ts ← Barrel racine core
│ ├── shared/ ← Alias : @shared
│ │ ├── components/
│ │ │ ├── button/
│ │ │ ├── modal/
│ │ │ └── index.ts ← Barrel
│ │ ├── directives/
│ │ │ └── index.ts
│ │ ├── pipes/
│ │ │ └── index.ts
│ │ └── index.ts ← Barrel racine shared
│ └── features/ ← Alias : @features
│ ├── dashboard/
│ │ ├── components/
│ │ ├── services/
│ │ └── index.ts
│ └── products/
│ ├── components/
│ ├── services/
│ └── index.ts
├── environments/ ← Alias : @env
│ ├── environment.ts
│ └── environment.prod.ts
└── types/ ← Declarations .d.ts globales
├── global.d.ts
└── modules.d.ts
tsconfig.json complet pour cette structure
// tsconfig.json — configuration complete pour un projet Angular moderne
{
"compilerOptions": {
// Cibles et modules
"target": "ES2022",
"module": "ESNext",
"moduleResolution": "bundler",
"lib": ["ES2022", "dom", "dom.iterable"],
// Qualite et securite du code
"strict": true,
"noUnusedLocals": true,
"noUnusedParameters": true,
"noImplicitOverride": true,
"noPropertyAccessFromIndexSignature": true,
// Interoperabilite avec les modules CJS de node_modules
"esModuleInterop": true,
// Force "import type" pour les types purs
"verbatimModuleSyntax": true,
// Declarations de types
"typeRoots": ["./node_modules/@types", "./src/types"],
// Alias de chemins
"baseUrl": ".",
"paths": {
"@core": ["src/app/core/index.ts"],
"@core/*": ["src/app/core/*"],
"@shared": ["src/app/shared/index.ts"],
"@shared/*": ["src/app/shared/*"],
"@features": ["src/app/features"],
"@features/*": ["src/app/features/*"],
"@env/*": ["src/environments/*"]
}
},
"include": [
"src/**/*.ts",
"src/**/*.d.ts"
],
"exclude": [
"node_modules",
"dist"
]
}
Exemple concret d'utilisation dans un composant
// src/app/features/dashboard/components/dashboard.component.ts
import { Component, OnInit, inject } from '@angular/core';
import { AsyncPipe, DatePipe } from '@angular/common';
// Imports via alias — lisibles et independants de la profondeur
import { AuthService, UserService } from '@core/services';
import type { User, UserProfile } from '@core/models';
import { ButtonComponent, ModalComponent } from '@shared/components';
import { TruncatePipe } from '@shared/pipes';
import { environment } from '@env/environment';
@Component({
selector: 'app-dashboard',
standalone: true,
// Imports des composants et pipes utilises dans le template
imports: [AsyncPipe, DatePipe, ButtonComponent, ModalComponent, TruncatePipe],
templateUrl: './dashboard.component.html'
})
export class DashboardComponent implements OnInit {
// Injection via la fonction inject() (Angular 14+)
private authService = inject(AuthService);
private userService = inject(UserService);
// Signal pour l'utilisateur courant
currentUser: UserProfile | null = null;
ngOnInit(): void {
// Recupere le profil depuis le service
this.userService.getProfile(this.authService.userId)
.subscribe(profile => {
this.currentUser = profile;
});
}
}
Accessibilite et responsive : bonnes pratiques
Une bonne organisation du code impact directement la qualite des composants produits. Des services correctement isoles facilitent la mise en place de composants accessibles et responsives.
- Les composants
@shared/componentsdoivent toujours inclure les attributs ARIA (aria-label,role,aria-expanded). - Les modeles
@core/modelspeuvent inclure des proprietes d'accessibilite (ariaLabel?: string). - Utiliser les grilles Bootstrap 4 (
col-12 col-md-6 col-lg-4) dans les templates des composants partages. - Tester les composants partages avec des lecteurs d'ecran pour garantir leur accessibilite.
Conclusion
Maitriser les modules TypeScript — ES Modules, import type, barrel exports, alias de chemins et fichiers de declaration — est ce qui separe un projet Angular maintenable d'un projet qui accumule de la dette technique. Ces outils, bien utilises ensemble, reduisent la complexite des imports, ameliorent le tree-shaking et rendent l'architecture lisible pour toute l'equipe.
Commencer par configurer verbatimModuleSyntax et moduleResolution: "bundler" dans le tsconfig.json, puis introduire progressivement les alias de chemins et les barrels granulaires. Surveiller les dependances circulaires avec madge des le debut du projet pour eviter les surprises.
@core/services, @shared/components) plutot qu'un index global qui re-exporte tout.