AngularAtlaséd. 22.2

Chap. 13Architecture à grande échelle59 / 75

Monorepo, bibliothèques et frontières

Un workspace avec plusieurs applications et bibliothèques : ng generate library, Nx, points d’entrée secondaires, publication et règles de dépendances.

architecture ~0 min de lecture Documentation officielle

Un monorepo est un seul dépôt git qui contient plusieurs projets : par exemple la boutique, le panneau d’administration et les bibliothèques qu’ils partagent. Ce n’est pas un monolithe : chaque application est construite et déployée séparément. Seul le code vit ensemble, avec une seule version d’Angular et un seul historique de commits.

Le gain : un changement qui touche une bibliothèque et les deux applications qui l’utilisent arrive dans une seule PR, testée en bloc. Le coût : sans outillage, chaque PR reconstruit et reteste tout ; sans règles, chacun importe n’importe quoi. Cette page couvre les deux moitiés : le workspace avec la CLI Angular et ng-packagr, puis ce que Nx ajoute par-dessus — le graphe de projets, affected, le cache, et des frontières vérifiées au lint.

En bref
  • Un monorepo réunit plusieurs applications et bibliothèques dans un seul dépôt, avec un seul jeu de versions de dépendances.
  • Angular CLI gère nativement plusieurs projets dans un workspace (ng generate library). Nx ajoute le graphe des dépendances, les frontières et le cache.
  • Une bibliothèque a une API publique : ce qui n’est pas exporté depuis son fichier d’entrée reste interne.
  • « Affected » lance le build et les tests seulement pour ce qu’un changement a touché.
  • La plupart du temps, un dossier suffit. Une bibliothèque se justifie quand elle est publiée ou utilisée par plusieurs applications.
Pourquoi c’est important

« Dossier, bibliothèque ou paquet npm » est une décision d’architecture, pas de style. Elle détermine la vitesse à laquelle vous livrez un changement transversal et la facilité avec laquelle vous pouvez casser quelque chose sans le remarquer. La documentation d’Angular le dit sans détour : une bibliothèque vous oblige à découpler le code de la logique de l’application, mais elle coûte du temps de maintenance — et cela devient rentable quand plusieurs applications l’utilisent.

Monorepo, polyrepo, monolithe

Les trois modèles diffèrent surtout par le coût d’un changement transversal. Imaginons que le modèle Product reçoive un nouveau champ, utilisé par la bibliothèque d’UI et par les deux applications :

ModèleÀ quoi cela ressembleModifier « Product »Le prix
MonolitheUne application, un déploiement, des dossiers par domaine1 PR, trivialNe convient pas à deux applications avec des rythmes de livraison différents
Polyrepo + npmChaque bibliothèque dans son propre dépôt, publiée avec une version3 PR ou plus : bibliothèque → publication → montée de version dans chaque applicationLes versions divergent ; un correctif n’arrive en production que lorsque chaque équipe « y pense »
MonorepoApplications + bibliothèques dans le même dépôt, consommées depuis le workspace1 PR atomique, testée avec tous ses consommateursLa CI doit être intelligente (affected, cache) et les frontières doivent être imposées

Un workspace de la CLI Angular a un seul package.json à la racine, donc tous les projets utilisent la même version d’Angular, de RxJS ou du CDK — ce que Nx appelle la single version policy. Un ng update met à jour tout le dépôt d’un coup. Le prix, c’est la coordination : aucune équipe ne prend de retard, mais aucune ne peut prendre d’avance. Si les équipes doivent vraiment livrer indépendamment, avec des rythmes et des versions différents, l’étape suivante est celle des micro frontends (qui, ironie du sort, vivent souvent eux aussi dans un monorepo).

Workspace multi-projets avec la CLI Angular

ng new crée par défaut une application à la racine. Pour un monorepo, vous partez d’un workspace vide (--no-create-application), et chaque ng generate application ou ng generate library ajoute un projet sous projects/ et une entrée dans angular.json. Une bibliothèque avec scope (@acme/ui) atterrit dans projects/acme/ui, et le nom du projet est le nom du paquet.

# 1. Workspace vide : aucune application à la racine (createApplication: false)
ng new acme --no-create-application
cd acme

# 2. Les projets atterrissent dans projects/ (newProjectRoot)
ng generate application shop
ng generate application admin
ng generate library @acme/ui          # → projects/acme/ui, préfixe par défaut "lib"

# 3. Chaque commande reçoit le nom du projet
ng serve shop
ng build admin
ng test @acme/ui
ng build @acme/ui --watch             # rebuild incrémental pendant que vous travaillez sur la bibliothèque

La différence importante se trouve dans angular.json : les applications utilisent le builder @angular/build:application (esbuild), les bibliothèques utilisent @angular/build:ng-packagr, qui produit un paquet au format Angular Package Format et compile toujours en AOT. Pour les bibliothèques, la configuration par défaut est production.

{
  "newProjectRoot": "projects",
  "projects": {
    "shop": {
      "projectType": "application",
      "root": "projects/shop",
      "sourceRoot": "projects/shop/src",
      "architect": {
        "build": { "builder": "@angular/build:application" },
        "serve": { "builder": "@angular/build:dev-server" }
      }
    },
    "@acme/ui": {
      "projectType": "library",
      "root": "projects/acme/ui",
      "sourceRoot": "projects/acme/ui/src",
      "prefix": "lib",
      "architect": {
        "build": {
          "builder": "@angular/build:ng-packagr",
          "defaultConfiguration": "production",
          "configurations": {
            "production": { "tsConfig": "projects/acme/ui/tsconfig.lib.prod.json" },
            "development": { "tsConfig": "projects/acme/ui/tsconfig.lib.json" }
          }
        }
      }
    }
  }
}
Les noms comptent si vous publiez

Évitez le préfixe ng- (il est réservé au framework). Pour les paquets publics, la convention est ngx- ; pour les internes, un scope d’entreprise (@acme/…) est le plus clair et convient à un registre privé.

Anatomie d’une bibliothèque

Quatre fichiers définissent une bibliothèque. Chacun répond à une question précise :

  • ng-package.json — où va le build (dest) et quel fichier contient l’API publique (lib.entryFile).
  • package.json — le nom, la version et les dépendances. Les @angular/* vont dans peerDependencies : s’ils étaient dans dependencies, l’application pourrait recevoir une seconde copie d’Angular et planter à l’exécution. "sideEffects": false indique au bundler qu’il peut supprimer tout ce que vous n’importez pas.
  • public-api.ts — le contrat. Ce qui n’est pas exporté ici est privé : vous pouvez donc le refactoriser sans prévenir personne.
  • tsconfig.lib.prod.json — compilationMode: "partial" : le format Ivy partiel est stable d’une version d’Angular à l’autre, et la compilation se termine dans le build de l’application, avec sa propre version du compilateur. D’où la règle : l’application doit utiliser la même version d’Angular que celle avec laquelle la bibliothèque a été construite, ou une version plus récente.
{
  "$schema": "../../../node_modules/ng-packagr/ng-package.schema.json",
  "dest": "../../../dist/acme/ui",
  "lib": {
    "entryFile": "src/public-api.ts"
  }
}

Points d’entrée secondaires

L’Angular Package Format produit surtout un module ES plat par point d’entrée. Un bundler ne peut pas découper un module en plusieurs chunks : le point d’entrée est donc la plus petite granularité du lazy loading. C’est pourquoi Angular Material a un point d’entrée par composant (@angular/material/button, …/tabs). La seconde raison : vous isolez les API particulières, comme @acme/ui/testing, pour qu’elles n’arrivent pas en production par accident.

projects/acme/ui/
├── ng-package.json          → @acme/ui             (point d’entrée principal)
├── src/public-api.ts
├── button/
│   ├── ng-package.json      → @acme/ui/button      (point d’entrée secondaire)
│   └── src/public-api.ts
└── testing/
    ├── ng-package.json      → @acme/ui/testing     (harnesses, mocks pour les tests)
    └── src/public-api.ts

ng-packagr découvre tout seul chaque ng-package.json ; vous n’enregistrez rien ailleurs.

Une bibliothèque à usage unique (un client HTTP, un ensemble d’utilitaires) fonctionne très bien avec un seul point d’entrée — et @angular/core n’en a qu’un pour le runtime.

Consommation, build et publication

Dans un même workspace, vous n’avez rien à publier, mais vous devez construire d’abord la bibliothèque. ng generate library ajoute à tsconfig.json un alias qui pointe vers dist/, pas vers les sources. La raison : les applications et les bibliothèques ont des builds différents (esbuild vs ng-packagr), et le même TypeScript peut donner un JavaScript différent. La documentation demande explicitement que les alias pointent vers la bibliothèque construite. Pendant que vous travaillez, ng build @acme/ui --watch reconstruit de façon incrémentale.

{
  "compilerOptions": {
    "paths": {
      // ajouté automatiquement par ng generate library — pointe vers le BUILD, pas vers les sources
      "@acme/ui": ["./dist/acme/ui"],
      // ajouté par vous, pour les points d’entrée secondaires
      "@acme/ui/*": ["./dist/acme/ui/*"]
    }
  }
}

Publier signifie un build en configuration production (celle par défaut) et un npm publish depuis dist — vous publiez le paquet généré, pas le dossier source.

# Build de distribution : la configuration production (celle par défaut pour les bibliothèques) → partial-Ivy
ng build @acme/ui

# Vous publiez le CONTENU de dist, pas le dossier source
cd dist/acme/ui
npm publish            # ou : npm publish --registry https://npm.company.com

# Le consommateur (un autre dépôt) l’installe comme n’importe quel autre paquet
npm install @acme/ui@^1.4.0
ConsommateurComment il obtient le codeVersions
Application dans le même dépôtUn alias dans tsconfig, toujours la dernière version de la brancheInutile : tout le monde est sur HEAD
Autre dépôt / autre clientnpm install @acme/ui depuis le registreSemver strict, un changelog, des peerDependencies correctes
Nx fait autrement, volontairement

Le générateur @nx/angular:lib place l’alias dans tsconfig.base.json en pointant vers les sources (libs/…/src/index.ts) : la bibliothèque est compilée avec l’application, sans étape de build séparée. Vous n’ajoutez son propre build (--buildable / --publishable) que pour les bibliothèques que vous publiez ou pour des builds incrémentaux. Ne mélangez pas les deux styles dans un même workspace sans savoir pourquoi.

Nx : le graphe et les frontières

Nx utilise les mêmes builders Angular, mais garde la configuration de chaque projet dans son propre project.json et construit un graphe de projets en analysant les imports. Tout le reste repose sur ce graphe : nx graph vous l’affiche, affected le parcourt à l’envers, et la règle de lint le vérifie à chaque import. Dans un workspace Angular CLI existant, vous commencez par npx nx@latest init.

# Workspace Angular CLI existant → Nx (conserve la structure)
npx nx@latest init
# …ou déplacez les projets dans apps/ et libs/
npx nx@latest init --integrated

# Des bibliothèques avec des tags dès le premier jour
nx g @nx/angular:lib libs/shop/feature-cart --tags=scope:shop,type:feature
nx g @nx/angular:lib libs/shared/ui --tags=scope:shared,type:ui
nx g @nx/angular:lib libs/shared/ui --publishable --importPath=@acme/ui   # uniquement si vous la publiez

# Le graphe de projets, interactif dans le navigateur
nx graph
nx graph --focus shop-feature-cart

# N’exécuter que ce qui est affecté par les changements par rapport à main
nx affected -t lint test build
nx affected -t build --graph        # voir le graphe de tâches avant de lancer
nx run-many -t test                 # tout, sans filtre

Types de bibliothèques

Le guide Nx propose quatre types — les mêmes rôles que dans smart vs dumb et dans la structure des dossiers, sauf qu’ici la frontière est une bibliothèque avec une API publique :

TypeCe qu’elle contientPeut dépendre de
featurePages et composants conteneurs pour un cas d’usage ; généralement en lazytout type
uiComposants de présentation ; les données arrivent par des inputs, aucun service injectéui, util
data-accessCommunication avec le backend et état (stores)data-access, util
utilFonctions pures, souvent sans code de frameworkutil
acme/
├── nx.json                     ← cache, targetDefaults, defaultBase
├── tsconfig.base.json          ← "@acme/shop/feature-cart": ["libs/shop/feature-cart/src/index.ts"]
├── apps/
│   ├── shop/project.json       tags: scope:shop,   type:app
│   └── admin/project.json      tags: scope:admin,  type:app
└── libs/
    ├── shop/
    │   ├── feature-catalog/    tags: scope:shop,   type:feature      (pages smart, lazy)
    │   ├── feature-cart/       tags: scope:shop,   type:feature
    │   └── data-access/        tags: scope:shop,   type:data-access  (API + store)
    ├── admin/
    │   ├── feature-users/      tags: scope:admin,  type:feature
    │   └── data-access/        tags: scope:admin,  type:data-access
    └── shared/
        ├── ui/                 tags: scope:shared, type:ui           (composants dumb)
        ├── data-access/        tags: scope:shared, type:data-access  (auth, config HTTP)
        └── util/               tags: scope:shared, type:util         (fonctions pures)

Des tags sur deux dimensions

Chaque projet reçoit des tags dans project.json. La combinaison qui marche en pratique est scope (le domaine : scope:shop, scope:admin, scope:shared) × type (la couche : type:feature, type:ui…). Le scope répond à « qui parle à qui », le type répond à « dans quel sens vont les dépendances ».

{
  "name": "shop-feature-cart",
  "sourceRoot": "libs/shop/feature-cart/src",
  "projectType": "library",
  "tags": ["scope:shop", "type:feature"]
}

La règle @nx/enforce-module-boundaries

La règle de lint prend une liste de depConstraints. Pour un import, Nx retient toutes les contraintes dont le projet source possède le sourceTag, et les applique avec un ET logique : shop/feature-cart doit satisfaire à la fois la règle de scope et la règle de type. onlyDependOnLibsWithTags exige que la cible ait au moins un des tags. Un projet sans aucun tag correspondant n’a le droit de dépendre de rien.

import nx from '@nx/eslint-plugin';

export default [
  ...nx.configs['flat/base'],
  ...nx.configs['flat/typescript'],
  ...nx.configs['flat/javascript'],
  {
    files: ['**/*.ts'],
    rules: {
      '@nx/enforce-module-boundaries': [
        'error',
        {
          allow: [],
          depConstraints: [
            // Dimension 1 : le domaine (qui peut parler à qui)
            { sourceTag: 'scope:shop', onlyDependOnLibsWithTags: ['scope:shop', 'scope:shared'] },
            { sourceTag: 'scope:admin', onlyDependOnLibsWithTags: ['scope:admin', 'scope:shared'] },
            { sourceTag: 'scope:shared', onlyDependOnLibsWithTags: ['scope:shared'] },
            // Dimension 2 : le type (le sens dans lequel circulent les dépendances)
            { sourceTag: 'type:app', onlyDependOnLibsWithTags: ['type:feature', 'type:ui', 'type:data-access', 'type:util'] },
            { sourceTag: 'type:feature', onlyDependOnLibsWithTags: ['type:feature', 'type:ui', 'type:data-access', 'type:util'] },
            { sourceTag: 'type:ui', onlyDependOnLibsWithTags: ['type:ui', 'type:util'] },
            { sourceTag: 'type:data-access', onlyDependOnLibsWithTags: ['type:data-access', 'type:util'] },
            { sourceTag: 'type:util', onlyDependOnLibsWithTags: ['type:util'] },
          ],
        },
      ],
    },
  },
];

En plus des tags, la règle a des vérifications fixes qui s’exécutent avant eux. Trois comptent au quotidien, dans cet ordre : les dépendances circulaires, l’import d’une application (interdit) et l’import statique d’une bibliothèque chargée en lazy (il tirerait le chunk dans le bundle principal). Vous pouvez tous les voir dans la démo :

LiveLe graphe du workspace et les règles de frontières (simulation)
appsshopappsadminshopfeature-catalogshopfeature-cartadminfeature-usersshopdata-accessshareduiadmindata-accessshareddata-accesssharedutil

scope:shopscope:sharedscope:adminlazy (loadChildren)

scope:shop, type:feature → scope:admin, type:data-access
Essayez :
Choisissez la source et la cible, puis cliquez sur « Lancer le lint sur l’import ». L’arête devient verte ou rouge, et en dessous vous voyez le message de la règle et chaque contrainte appliquée. Le moteur reproduit les vérifications et les messages de @nx/enforce-module-boundaries, dans le même ordre ; il n’exécute pas le vrai ESLint.

La même règle accepte aussi des formes plus fines :

// Autres formes utiles de contraintes (même règle, mêmes depConstraints) :
[
  // "ui" ne doit pas atteindre data-access, même TRANSITIVEMENT (vérifie toute la chaîne)
  { sourceTag: 'type:ui', notDependOnLibsWithTags: ['type:data-access'] },

  // la règle ne s’applique qu’aux projets qui ont LES DEUX tags
  { allSourceTags: ['scope:shop', 'type:ui'], onlyDependOnLibsWithTags: ['scope:shared'] },

  // paquets npm interdits pour un type de projet
  { sourceTag: 'type:util', bannedExternalImports: ['@angular/*'] },

  // glob et regex dans les tags
  { sourceTag: 'scope:*', onlyDependOnLibsWithTags: ['/^scope:(shared|core)$/'] },
]

Attention à la différence : notDependOnLibsWithTags vérifie toute la chaîne des dépendances de la cible, pas seulement la dépendance directe. C’est le bon outil pour « l’UI n’atteint jamais data-access, même par des détours ». Pour les frontières entre dossiers dans une seule application, sans Nx, voir Boundaries.

Affected et cache

nx affected -t test part des fichiers modifiés entre --base (par défaut defaultBase, c’est-à-dire main) et --head, les associe à des projets, puis remonte le graphe le long des arêtes inverses : tout ce qui dépend, directement ou transitivement, d’un projet modifié est affecté. Dans le workspace de la démo, un changement dans shop/data-access affecte 4 projets (la bibliothèque, les deux features de la boutique et l’application boutique) ; un changement dans shared/util les affecte tous les 10.

Vient ensuite le cache : avant d’exécuter une tâche, Nx calcule un hash de ses entrées — les sources du projet, celles de ses dépendances (selon inputs), la configuration, la commande. Si le hash a déjà été vu, Nx rejoue le résultat (la sortie du terminal et les fichiers) au lieu de l’exécuter. Avec un cache distant, une tâche exécutée une fois en CI ne s’exécute plus sur le portable de personne.

Livenx affected -t test, avec cache (simulation)
appsshopappsadminshopfeature-catalogshopfeature-cartadminfeature-usersshopdata-accessshareduiadmindata-accessshareddata-accesssharedutil

modifié ✎affectéexécuté ✓depuis le cache

0/10
projets affectés
0s
test sur affected (vs 3m 51s tous)

Cliquez sur un projet du graphe : vous modifiez l’un de ses fichiers.

  • Le terminal de la CI s’affiche ici.
Cliquez sur shared/util : presque tout le dépôt devient affecté. Lancez, puis relancez — la seconde fois, tout vient du cache. Modifiez ensuite shop/feature-cart et lancez : seuls feature-cart et shop s’exécutent, le reste est un cache hit. « fusionner dans main » déplace la base ; « nx reset » vide le cache.
{
  "defaultBase": "main",
  "namedInputs": {
    "default": ["{projectRoot}/**/*"],
    "production": ["default", "!{projectRoot}/**/*.spec.ts"]
  },
  "targetDefaults": {
    "build": {
      "dependsOn": ["^build"],
      "inputs": ["production", "^production"],
      "cache": true
    },
    "test": {
      "inputs": ["default", "^production"],
      "cache": true
    },
    "lint": { "cache": true }
  }
}
La granularité rend affected précis

Si chaque application dépend d’une énorme bibliothèque shared, tout changement dedans affecte tout le dépôt, et affected n’économise plus rien. Des bibliothèques plus petites, découpées par domaine et par type, donnent des graphes plus étroits et davantage de cache hits.

Le cache suppose des tâches sans effets de bord : les mêmes entrées donnent le même résultat. Un test e2e qui interroge un vrai backend n’est pas cacheable — son résultat dépend de quelque chose que Nx ne voit pas.

Dossier ou bibliothèque ?

Le guide Nx sur la taille des projets résume bien l’arbitrage. Vous découpez en bibliothèques quand les tests vous ralentissent (affected et le cache commencent à sauter des morceaux), quand le code a un autre propriétaire (une équipe, un fichier CODEOWNERS), ou quand vous voulez des frontières vérifiées. Vous ne découpez pas ce qui change toujours ensemble — la frontière coûte en navigation et en configuration sans rien apporter. Et tant que la conception est encore en exploration, un seul projet est moins coûteux ; vous extrayez la bibliothèque quand l’API s’est stabilisée.

LiveDossier, bibliothèque interne ou paquet publiable ?
Score : -3
Un dossier suffit

Un dossier dans la feature (ou dans shared/) avec un index.ts comme API publique vous donne 80 % des bénéfices sans configuration. Extrayez la bibliothèque quand un second consommateur apparaît.

src/app/features/cart/ui/ + règle de lint sur les imports
Cochez ce qui est vrai pour votre code. Le score est une heuristique construite sur le guide Nx sur la taille des projets, pas une loi. La même question au niveau du dossier : la page Shared vs feature.

Pour l’option « dossier », les règles de Shared vs feature et un index.ts comme API publique vous donnent l’essentiel des bénéfices, sans configuration supplémentaire.

Quand OUI, quand NON

Monorepo avec bibliothèques
  • Deux applications ou plus partagent du code de domaine ou un design system interne.
  • Changements transversaux fréquents (modèles d’API, auth, thème) que vous voulez livrer de façon atomique, en une seule PR.
  • Plusieurs équipes dans le même code : les tags et la règle de lint rendent les frontières explicites.
  • Une CI lente : affected + cache réduisent le temps proportionnellement à ce que vous avez touché.
  • Un design system consommé aussi hors du dépôt : une bibliothèque publiable construite avec ng-packagr.
Rester avec une application et des dossiers
  • Une application, une équipe : des dossiers par domaine (Structure des dossiers) et une règle no-restricted-imports vous donnent les mêmes frontières.
  • Des équipes qui doivent livrer de façon totalement indépendante, même avec des versions d’Angular différentes : polyrepo ou micro frontends.
  • Une bibliothèque par composant : de la configuration sans bénéfice. Une bibliothèque par domaine et par type, pas par fichier.
  • Publier sur npm du code utilisé uniquement dans le dépôt : les versions ajoutent du cérémonial sans valeur.

Pièges fréquents

Un alias vers les sources de la bibliothèque dans un workspace Angular CLI

"@acme/ui": ["projects/acme/ui/src/public-api.ts"] semble pratique (vous ne construisez plus la bibliothèque), mais l’application compile le code avec esbuild, alors que les consommateurs externes reçoivent ce que produit ng-packagr. Les bugs n’apparaissent que chez eux. Solution : un alias vers dist et ng build @acme/ui --watch — ou passez à Nx, conçu pour compiler depuis les sources.

@angular/core dans dependencies

La bibliothèque apporte sa propre copie d’Angular, et l’application se retrouve avec deux instances : la DI et la Change Detection cassent de façons difficiles à diagnostiquer. Solution : tout ce qui est @angular/* va dans peerDependencies.

  • Imports profonds (@acme/ui/src/lib/button/button) qui contournent public-api.ts : le premier refactoring interne casse les consommateurs. Solution : exportez depuis l’API publique ou créez un point d’entrée secondaire ; interdisez ce motif avec no-restricted-imports.
  • Imports relatifs entre points d’entrée d’une même bibliothèque : ng-packagr construit chaque point d’entrée séparément et le build échoue. Solution : importez par le nom du paquet (@acme/ui).
  • Des projets sans tags, « réparés » avec sourceTag: '*' → '*' : la règle ne vérifie plus rien. Solution : --tags à la génération, et les tags vérifiés en revue.
  • Une bibliothèque partagée fourre-tout dont tout dépend : affected = tout le dépôt à chaque PR. Solution : découpez-la par domaine et par type.
  • Mettre en cache des tâches non déterministes (e2e contre un vrai backend, tests qui lisent l’heure) : Nx rejoue un résultat qui n’est plus vrai. Solution : pas de cache: true sur ces targets, ou des inputs complets.
  • Un import statique au-dessus d’un loadChildren : le chunk lazy disparaît dans le bundle initial. La règle Nx l’attrape ; sans Nx, vérifiez le bundle (voir Performance).

Aide-mémoire

ANGULAR CLI (multi-projets)
  ng new acme --no-create-application        workspace vide
  ng g application shop | ng g library @acme/ui
  ng build @acme/ui [--watch]                 les bibliothèques sont construites AVANT d’être consommées
  tsconfig paths → ./dist/acme/ui             (+ "@acme/ui/*" pour les points d’entrée secondaires)
  cd dist/acme/ui && npm publish              seul ce qui quitte le dépôt est publié

BIBLIOTHÈQUE
  ng-package.json        dest + lib.entryFile          (secondaire : uniquement "lib")
  public-api.ts          l’API publique — le reste est privé
  package.json           peerDependencies pour @angular/*, "sideEffects": false
  compilationMode        "partial" pour npm

NX
  nx graph                         le graphe de projets
  nx affected -t lint test build   uniquement les projets affectés (dépendants transitifs inclus)
  "cache": true in targetDefaults  hash des inputs → rejeu à la deuxième exécution
  tags + @nx/enforce-module-boundaries → frontières imposées au lint
  types : feature → ui, data-access, util · ui → ui, util · data-access → data-access, util · util → util

Chap. 13 · 0 sur 4 terminées

Leçon terminée ?

Marquez-la : elle apparaîtra sur votre carte, dans le sommaire et sur la page d’accueil.

Se marque toute seule une fois la leçon lue en entier.

  1. Micro frontends
  2. Web Components avec Angular Elements
  3. Monorepo, bibliothèques et frontières
  4. Architecture hexagonale (ports et adapters)
Voir toute votre carte

Les 75 pages · tapez pour filtrer

  • 01Nouveautés d’Angular 21 & 22Tous les changements importants de la v21 et de la v22, en bref, avec un lien vers la page qui explique chacun.Départ
  • 01Votre première application, pas à pasUne liste de lecture construite d’un composant vide jusqu’à une application terminée — signals, @for, inputs et outputs, un service et un formulaire, pas à pas.Départ
  • 01Décisions rapides« Où mettre ça ? », « Signal ou Observable ? », « Shared ou feature ? » — des réponses courtes aux questions de tous les jours.Départ
  • 01Questions d’entretien Angular60 vraies questions d’entretien pour profils junior, confirmé et senior, avec la réponse à donner, la relance et les signaux d’alerte. Plus un entretien blanc.Départ
  • 02Standalone et bootstrapComment une application démarre sans NgModule : bootstrapApplication, app.config.ts, imports dans le composant.Fondamentaux modernes
  • 02Data binding et événementsInterpolation, bindings de propriétés et d’attributs, événements avec $event et filtres de touches, et comment [( )] est construit — ce qui entre dans le DOM et ce qui en revient.Fondamentaux modernes
  • 02Control Flow : @if, @for, @switchLa syntaxe de blocs qui remplace *ngIf/*ngFor/ngSwitch, avec track, @empty et vérifications d’exhaustivité.Fondamentaux modernes
  • 02@let et les nouvelles expressions de templateVariables locales avec @let, fonctions fléchées, spread, regex, instanceof, commentaires dans les balises.Fondamentaux modernes
  • 02@defer — Lazy Loading dans les templatesChargez les composants lourds uniquement quand c’est nécessaire : triggers, prefetch, placeholder, loading, error.Fondamentaux modernes
  • 02Bindings de classe et de stylePourquoi [class.x] et [style.x] ont remplacé ngClass et ngStyle (avec migration automatique depuis la v21).Fondamentaux modernes
  • 02Styles des composants et encapsulation:host, les trois modes d’encapsulation, les thèmes avec des variables CSS, et pourquoi ::ng-deep est un dernier recours.Fondamentaux modernes
  • 02Pipes : quand et commentPipes pures vs impures, pipes personnalisées, et comment choisir entre une pipe, computed() et une méthode.Fondamentaux modernes
  • 03signal() et computed()Les primitives réactives : état modifiable, état dérivé, égalité, untracked et règles d’immutabilité.Signals et réactivité
  • 03effect() — quand OUI, quand NONEffets de bord synchronisés avec les Signals : localStorage, logs, API impératives. Et leurs pièges.Signals et réactivité
  • 03linkedSignal() — état dérivé modifiableUn signal qui se réinitialise quand sa source change, mais que vous pouvez aussi modifier à la main.Signals et réactivité
  • 03resource, rxResource, httpResourceDes données asynchrones sous forme de Signals : status, value, error, reload, annulation automatique. Stable depuis la v22.Signals et réactivité
  • 03debounced() — signals différésLa version « debounce » d’un signal, sans RxJS. Parfait pour la recherche.Signals et réactivité
  • 03Listes : rechercher, filtrer, trier, paginerUne chaîne de signals computed qui ne recalcule que ce qui a changé, avec les filtres dans l’URL pour qu’un lien rouvre la même vue.Signals et réactivité
  • 03Les Observables depuis zéroCe qu’est un Observable, subscribe et unsubscribe, les opérateurs que vous utilisez chaque semaine, froid ou chaud — et quand un signal suffit.Signals et réactivité
  • 03Signals ↔ RxJStoSignal, toObservable, takeUntilDestroyed, outputFromObservable — et quand RxJS reste le bon choix.Signals et réactivité
  • 04input() et output()Les inputs comme Signals (required, transform, alias) et les outputs typés. Les données descendent, les événements remontent.Composants : communication
  • 04model() — binding bidirectionnelLe « banana-in-a-box » [( )] avec une seule API. Quand l’utiliser, et quand input + output est préférable.Composants : communication
  • 04viewChild et contentChildLes queries comme Signals : viewChild, viewChildren, contentChild, contentChildren, l’option read.Composants : communication
  • 04Lifecycle et DestroyRefQuels hooks comptent encore à l’ère des Signals, afterNextRender, DestroyRef et ngOnChanges typé.Composants : communication
  • 04Projection de contenung-content avec select, plusieurs slots, contenu par défaut et le piège de la projection conditionnelle.Composants : communication
  • 04ng-template et ngTemplateOutletLes templates comme valeurs : les render props en Angular, contexte typé et personnalisation des composants.Composants : communication
  • 05Smart vs DumbContainer vs présentation : qui détient l’état, qui se contente d’afficher, et comment bien les séparer.Conception des composants
  • 05Composants génériques <T>Un DataTable<T> ou un Select<T> correctement typé, avec des templates pour les cellules.Conception des composants
  • 05Composants dynamiquesNgComponentOutlet, createComponent avec inputBinding/outputBinding, et un tableau de bord construit à partir d’une configuration.Conception des composants
  • 05Directives et composition d’hôteLes directives d’attribut, les métadonnées host: {} et la Directive Composition API (hostDirectives).Conception des composants
  • 05Contrôles de formulaire personnalisésFormValueControl (Signal Forms) vs ControlValueAccessor : une notation par étoiles, dans les deux variantes.Conception des composants
  • 05Change DetectionOnPush est la valeur par défaut depuis la v22, Zone.js a disparu depuis la v21. Ce qui déclenche le rendu et comment éviter les mauvaises surprises.Conception des composants
  • 06inject() et @Service()Le nouveau décorateur @Service() de la v22, inject() vs le constructeur, et le contexte d’injection.Dependency Injection
  • 06Providers et hiérarchie des injecteursRoot vs route vs composant, useClass/useValue/useFactory/useExisting, et les modificateurs optional/self/skipSelf.Dependency Injection
  • 06InjectionToken et provideX()Configuration typée, fonctions provideFeature(), makeEnvironmentProviders et initializers.Dependency Injection
  • 06injectAsync() — services en Lazy LoadingChargez un service lourd uniquement quand vous en avez besoin, avec prefetch à l’idle.Dependency Injection
  • 07Structure des dossierscore / shared / features, ce que contient chacun, et à quoi ressemble l’intérieur d’une feature.Architecture d’application
  • 07Shared vs featureQuand déplacer un composant dans shared et (surtout) quand NE PAS le faire. Une checklist concrète.Architecture d’application
  • 07Interfaces et modèlesInterface en ligne ou fichier séparé ? DTO vs modèle de domaine, type vs interface, où les ranger.Architecture d’application
  • 07State Management avec les SignalsStores simples à base de Signals, facades, état local vs global, et quand NgRx devient nécessaire.Architecture d’application
  • 07Guide de style et nommageLes conventions officielles actuelles : noms de fichiers sans suffixes, protected, readonly, ordre des membres.Architecture d’application
  • 07Barrels, alias et frontièresindex.ts, alias de chemins, API publique d’une feature et règles de dépendances vérifiables automatiquement.Architecture d’application
  • 07Erreurs : @boundary et ErrorHandlerLe nouveau bloc @boundary de la v22.2, l’ErrorHandler global, les erreurs HTTP et les erreurs de resource.Architecture d’application
  • 08Routing moderne et Lazy LoadingloadComponent, routes enfants, paramètres comme inputs et héritage des paramètres (le nouveau comportement par défaut de la v22).Routing
  • 08Guards et resolverscanActivate, canMatch, canDeactivate et resolve sous forme de fonctions, avec redirections via UrlTree / RedirectCommand.Routing
  • 09Signal Forms — les basesDes formulaires bâtis sur les Signals : form(), [formField], validation par schéma. Stable depuis la v22.Formulaires
  • 09Signal Forms — avancéValidation personnalisée et inter-champs, async/HTTP avec debounce, listes dynamiques, désactivé/masqué, schémas réutilisables.Formulaires
  • 09Reactive Forms typésFormGroup typé, nonNullable, FormArray — et leur cohabitation avec les Signal Forms.Formulaires
  • 10HttpClient, interceptors et couche d’APIHttpClient sans configuration (v21), Fetch par défaut (v22), interceptors fonctionnels et une couche d’API avec mapping des DTO.HTTP et données
  • 11Performance — une checklistCe qui compte vraiment : OnPush + Signals, track, @defer, routes en Lazy Loading, images, bundles et Hydration.Qualité et performance
  • 11Tests avec VitestVitest est le runner par défaut depuis la v21. TestBed, composants avec Signals, services, HTTP et harnesses.Qualité et performance
  • 11DevTools : regarder dans votre applicationChrome DevTools et Angular DevTools pas à pas : ce que montre chaque panneau, avec des exercices qui fonctionnent sur ce site.Qualité et performance
  • 11Angular AriaDes composants headless et accessibles que vous stylez comme vous voulez : tabs, accordion, menu, listbox...Qualité et performance
  • 12SSR, Prerendering et modes de renduQuand rendre sur le serveur, quand au build et quand uniquement dans le navigateur : RenderMode par route, app.routes.server.ts et les coûts réels.SSR et Hydration
  • 12Hydration : event replay et incrémentaleComment le navigateur reprend le HTML du serveur sans le re-rendre : Hydration complète, event replay, @defer (hydrate on …) et erreurs de mismatch.SSR et Hydration
  • 12Code compatible serveurwindow n’existe pas sur le serveur : PLATFORM_ID, afterNextRender, DOCUMENT, TransferState, le cache HTTP et PendingTasks.SSR et Hydration
  • 13Micro frontendsQuand découper une application en plusieurs déploiements vaut le coup, et quand c’est juste de la complexité : Native Federation, Module Federation, iframes, Web Components.Architecture à grande échelle
  • 13Web Components avec Angular ElementscreateCustomElement : un composant Angular utilisé comme balise HTML n’importe où, plus la communication entre applications via des événements et BroadcastChannel.Architecture à grande échelle
  • 13Monorepo, bibliothèques et frontièresUn workspace avec plusieurs applications et bibliothèques : ng generate library, Nx, points d’entrée secondaires, publication et règles de dépendances.Architecture à grande échelle
  • 13Architecture hexagonale (ports et adapters)Un domaine sans Angular, des ports sous forme de classes abstraites et des adapters interchangeables : HTTP, localStorage ou en mémoire, sans toucher à l’UI.Architecture à grande échelle
  • 14Les Signals sous le capotLe graphe réactif : producteurs et consommateurs, push-pull, pourquoi computed est lazy et sans glitch, le problème du diamant et quand les effects s’exécutent.Réactivité avancée
  • 14RxJS avancé : opérateurs d’ordre supérieurswitchMap, mergeMap, concatMap et exhaustMap sur un diagramme interactif, plus retry avec backoff, shareReplay et comment éviter les fuites.Réactivité avancée
  • 14NgRx SignalStoreState Management à grande échelle sur les Signals : withState, withComputed, withMethods, rxMethod, entités et hooks — et quand un store simple suffit.Réactivité avancée
  • 14Machines à états avec les signalsFini les états impossibles : une machine typée sur des signals, avec transitions, gardes et un diagramme d’états en direct.Réactivité avancée
  • 15WebSockets : un temps réel qui résiste au réseauUn socket typé sous forme de signals : reconnexion avec backoff et jitter, heartbeat, file d’attente hors ligne et sécurité SSR.Temps réel et hors ligne
  • 15Mises à jour optimistes et rollbackUne UI instantanée qui reste honnête : snapshot et rollback, réponses dans le désordre, clés d’idempotence.Temps réel et hors ligne
  • 15Offline-first : Service Worker et cacheLe Service Worker d’Angular : stratégies de cache, annonce des nouvelles versions et file d’attente pour les modifications faites hors ligne.Temps réel et hors ligne
  • 16Web WorkersDes calculs lourds sans bloquer l’UI : un vrai worker dans la page, l’échange de messages et quand ça n’en vaut pas la peine.UI avancée et performance
  • 16Listes géantes : virtual scroll100 000 lignes sans latence : le virtual scroll du CDK, itemSize, trackBy et les alternatives (pagination, scroll infini).UI avancée et performance
  • 16Animations modernes et View Transitionsanimate.enter et animate.leave à la place de @angular/animations (déprécié), transitions CSS et View Transitions à la navigation.UI avancée et performance
  • 16Fuites mémoire : les trouver et les corrigerAbonnements, timers et listeners qui survivent à leur composant — comment les repérer dans les DevTools et les fermer.UI avancée et performance
  • 17Sécurité : XSS, sanitisation et CSPCe qu’Angular fait automatiquement contre le XSS, quand bypassSecurityTrust devient dangereux, plus la CSP avec nonce, Trusted Types et XSRF.Sécurité et internationalisation
  • 17Authentification : tokens et refreshUn interceptor qui ajoute le token, le renouvelle une seule fois sur une 401 et rejoue les requêtes en attente — plus où conserver le token.Sécurité et internationalisation
  • 17OAuth 2.1 et OpenID Connect avec PKCELe flux authorization code avec PKCE, pas à pas : verifier et challenge, state et nonce, l’ID token, et pourquoi un BFF est plus sûr.Sécurité et internationalisation
  • 17Internationalisation (i18n)$localize, l’attribut i18n, ICU plural/select, un build par langue vs des traductions à l’exécution, et le formatage selon la locale.Sécurité et internationalisation