{"id":18522930,"url":"https://github.com/avine/geekle-angular-global-summit-2023","last_synced_at":"2026-05-02T02:34:40.863Z","repository":{"id":120339957,"uuid":"602460098","full_name":"avine/geekle-angular-global-summit-2023","owner":"avine","description":"Architecture Micro Frontends pour Angular","archived":false,"fork":false,"pushed_at":"2023-05-31T12:25:58.000Z","size":317,"stargazers_count":1,"open_issues_count":0,"forks_count":1,"subscribers_count":2,"default_branch":"main","last_synced_at":"2025-05-14T18:54:06.680Z","etag":null,"topics":["angular","micro-frontends"],"latest_commit_sha":null,"homepage":"","language":"TypeScript","has_issues":true,"has_wiki":null,"has_pages":null,"mirror_url":null,"source_name":null,"license":"mit","status":null,"scm":"git","pull_requests_enabled":true,"icon_url":"https://github.com/avine.png","metadata":{"files":{"readme":"README.md","changelog":null,"contributing":null,"funding":null,"license":"LICENSE","code_of_conduct":null,"threat_model":null,"audit":null,"citation":null,"codeowners":null,"security":null,"support":null,"governance":null,"roadmap":null,"authors":null,"dei":null,"publiccode":null,"codemeta":null}},"created_at":"2023-02-16T08:59:58.000Z","updated_at":"2024-10-28T00:38:47.000Z","dependencies_parsed_at":null,"dependency_job_id":"5a43b694-0429-49f3-b5a7-ac71fefd1b88","html_url":"https://github.com/avine/geekle-angular-global-summit-2023","commit_stats":null,"previous_names":[],"tags_count":0,"template":false,"template_full_name":null,"purl":"pkg:github/avine/geekle-angular-global-summit-2023","repository_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/avine%2Fgeekle-angular-global-summit-2023","tags_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/avine%2Fgeekle-angular-global-summit-2023/tags","releases_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/avine%2Fgeekle-angular-global-summit-2023/releases","manifests_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/avine%2Fgeekle-angular-global-summit-2023/manifests","owner_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners/avine","download_url":"https://codeload.github.com/avine/geekle-angular-global-summit-2023/tar.gz/refs/heads/main","sbom_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/avine%2Fgeekle-angular-global-summit-2023/sbom","scorecard":null,"host":{"name":"GitHub","url":"https://github.com","kind":"github","repositories_count":286080680,"owners_count":32521108,"icon_url":"https://github.com/github.png","version":null,"created_at":"2022-05-30T11:31:42.601Z","updated_at":"2026-05-02T01:12:54.858Z","status":"online","status_checked_at":"2026-05-02T02:00:05.923Z","response_time":132,"last_error":null,"robots_txt_status":"success","robots_txt_updated_at":"2025-07-24T06:49:26.215Z","robots_txt_url":"https://github.com/robots.txt","online":true,"can_crawl_api":true,"host_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub","repositories_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories","repository_names_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repository_names","owners_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners"}},"keywords":["angular","micro-frontends"],"created_at":"2024-11-06T17:33:34.747Z","updated_at":"2026-05-02T02:34:40.836Z","avatar_url":"https://github.com/avine.png","language":"TypeScript","funding_links":[],"categories":[],"sub_categories":[],"readme":"# Architecture Micro Frontends pour Angular\n\nJ'étais présent à l'\"Angular Global Summit' 2023\" qui s'est déroulé les 14 et 15 février 2023.\n\nLors de cet événement en ligne, j'ai suivi avec intérêt la présentation de [Manfred Stayer](https://at.linkedin.com/in/manfred-steyer-84645821) sur les Micro Frontends.\nLa solution qu'il propose pour Angular utilise le [module-federation-plugin](https://github.com/angular-architects/module-federation-plugin) et exploite le \"Module Federation\" introduit dans [Webpack 5](https://webpack.js.org/).\n\n## Cas d'usage des Micro Frontends\n\nLa promesse des Micro Frontends est de permettre à plusieurs équipes de travailler en parallèle sur des applications distinctes (typiquement, une application par fonctionnalité métier, pour un découpage en domaines - DDD).\nChacune de ces applications peut ainsi avoir son propre cycle de développement et de déploiement, rendant chaque équipe (presque) totalement autonome.\n\nDans cet article, je vais vous montrer comment configurer un workspace (monorepo) Angular contenant :\n\n- une application principale appelée \"shell\"\n- un application Micro Frontend appelée \"remote1\"\n- une librairie utilisée par le shell et le remote1 appelée \"shared\"\n\nOn peut donc imaginer qu'aujourd'hui 2 équipes distinctes travaillent respectivement sur le shell et le remote1.\nEt rien ne nous empêche demain, d'ajouter d'autres équipes pour travailler sur les Micro Frontends remote2, remote3, ...\n\nN'hésitez-pas à jeter un coup d'oeil au [code source de l'application](https://github.com/avine/geekle-angular-global-summit-2023) sur le repo GitHub.\n\n## Un peu d'échafaudages\n\nCommençons par créer les 3 grandes parties de notre monorepo, à l'aide de la CLI d'Angular :\n\n```shell\n# Créons un nouveau workspace Angular...\nng new shell\n\n# Ajoutons une seconde application à notre workspace\nng generate application remote1\n\n# Et une librarie transverse\nng generate library shared\n```\n\nPour faire simple, disons que notre librairie \"shared\" expose de la donnée via un service :\n\n```ts\n// projects/shared/src/lib/shared.service.ts\nimport { Injectable } from '@angular/core';\nimport { BehaviorSubject } from 'rxjs';\n\nexport interface SharedData {\n  value: string;\n}\n\n@Injectable({\n  providedIn: 'root',\n})\nexport class SharedService {\n  private _data$ = new BehaviorSubject\u003cSharedData | null\u003e(null);\n\n  data$ = this._data$.asObservable();\n\n  setData(data: SharedData) {\n    this._data$.next(data);\n  }\n}\n```\n\nNotez qu'au départ la donnée est `null`.\nNous allons donc appeler la méthode `setData` depuis le \"shell\" :\n\n```ts\n// src/app/app.component.ts\nimport { Component, inject, OnInit } from '@angular/core';\nimport { RouterOutlet } from '@angular/router';\nimport { SharedService } from '@demo/shared';\n\n@Component({\n  selector: 'app-root',\n  standalone: true,\n  imports: [RouterOutlet],\n  template: ` \u003crouter-outlet /\u003e `,\n})\nexport class AppComponent implements OnInit {\n  private sharedService = inject(SharedService);\n\n  ngOnInit(): void {\n    this.sharedService.setData({ value: 'Hello Micro Frontend' });\n  }\n}\n```\n\nEnsuite, nous allons afficher le contenu de `data$` depuis le \"remote1\" :\n\n```ts\n// projects/remote1/src/app/home.component.ts\nimport { AsyncPipe, JsonPipe } from '@angular/common';\nimport { Component, inject } from '@angular/core';\nimport { SharedService } from '@demo/shared';\n\n@Component({\n  selector: 'app-home',\n  standalone: true,\n  imports: [AsyncPipe, JsonPipe],\n  template: `\u003cp\u003eData exposed to Remote 1: {{ data$ | async | json }}\u003c/p\u003e`,\n})\nexport class HomeComponent {\n  protected data$ = inject(SharedService).data$;\n}\n```\n\nNotez bien que pour le moment, même si les applications \"shell\" et \"remote1\" utilisent toutes deux le code de la librairie transverse \"shared\", elles n'en restent pas moins indépendantes.\nVous pouvez donc les servir en parallèle sur 2 ports différents :\n\n```shell\nng serve shell --port 4200 \u0026 ng serve remote1 --port 4201\n```\n\nSi on visite l'URL http://localhost:4201, on obtient logiquement le résultat suivant :\n\n```html\n\u003cp\u003eData exposed to Remote 1: null\u003c/p\u003e\n```\n\n## Un peu de magie\n\nNous allons utiliser la schematic [@angular-architects/module-federation](https://www.npmjs.com/package/@angular-architects/module-federation) pour ajouter des supers pouvoirs à nos applications :\n\n```shell\nng add @angular-architects/module-federation --project shell --port 4200 --type dynamic-host\nng add @angular-architects/module-federation --project remote1 --port 4201 --type remote\n```\n\nL'argument `--type dynamic-host` indique que l'application \"shell\" est bien l'ossature de notre architecture Micro Frontends.\nEt l'argument `--type remote` indique naturellement que l'application \"remote1\" sera accessible en tant que Micro Frontend depuis le \"shell\".\n\nPlusieurs choses sont à noter dans le `package.json` que la schematic a modifié :\n\n```json\n{\n  \"name\": \"shell\",\n  \"scripts\": {\n    \"run:all\": \"node node_modules/@angular-architects/module-federation/src/server/mf-dev-server.js\"\n    ...\n  },\n  \"dependencies\": {\n    \"@angular-architects/module-federation\": \"^15.0.3\",\n    ...\n  },\n  \"devDependencies\": {\n    \"ngx-build-plus\": \"^15.0.0\",\n    ...\n  }\n}\n```\n\nLa commande `npm run run:all` a été ajoutée.\nElle permet de lancer en parallèle toutes les applications du workspace.\nC'est l'équivalent de la commande `ng serve shell --port 4200 \u0026 ng serve remote1 --port 4201` que nous avons utilisée plus haut...\n\nLe paquet [ngx-build-plus](https://www.npmjs.com/package/ngx-build-plus) a été installé.\nCelui-ci permet de surcharger la configuration Webpack de n'importe quel projet du workspace.\nC'est très utile, car dans Angular ces fichiers de configuration ne sont pas accessibles car masqués par la CLI d'Angular.\n\n## La surchage des configurations Webpack\n\nVoyons maintenant la surcharge des configurations Webpack.\n\nPour le \"shell\" :\n\n```js\n// webpack.config.js\nconst { shareAll, withModuleFederationPlugin } = require('@angular-architects/module-federation/webpack');\n\nmodule.exports = withModuleFederationPlugin({\n  shared: {\n    ...shareAll({ singleton: true, strictVersion: true, requiredVersion: 'auto' }),\n  },\n});\n```\n\nEt pour le \"remote1\" :\n\n```js\n// projects/remote1/webpack.config.js\nconst { shareAll, withModuleFederationPlugin } = require('@angular-architects/module-federation/webpack');\n\nmodule.exports = withModuleFederationPlugin({\n  name: 'remote1',\n\n  exposes: {\n    // Notez que dans cet exemple, nous exposons un fichier de Routes qui sera donc chargé avec `loadChildren`\n    './Routes': './projects/remote1/src/app/app.routes.ts',\n\n    // Mais on aurait pu exposer un composant (standalone) qui serait alors chargé avec `loadComponent`\n    /*'./HomeComponent': './projects/remote1/src/app/home.component.ts', */\n  },\n\n  shared: {\n    ...shareAll({ singleton: true, strictVersion: true, requiredVersion: 'auto' }),\n  },\n});\n```\n\nMais avant d'expliquer le contenu de ces fichiers, voyons ce que contient l'application \"remote1\" une fois compilée.\n\nSi nous compilons l'application \"remote1\" avec la commande `ng build remote1` nous pouvons noter la présence d'un fichier spécial :\n`dist/remote1/remoteEntry.js`.\nC'est le fichier \"magique\" de notre Micro Frontend qui va être exploité par l'application \"shell\" (pour justement charger le \"remote1\") !\n\nRevenons maintenant au contenu de ces fichiers.\n\nLa section `shared: { ...shareAll({ ... }) }` indique à Webpack que toutes les dépendances doivent être partagées.\nCela signifie par exemple que la dépendance `@angular/core` sera bien inclue dans le bundle du \"shell\", mais pas dans le `remoteEntry.js` du \"remote1\".\nPour fonctionner en tant que Micro Frontend, \"remote1\" compte donc sur le \"shell\" pour lui fournir l'implémentation de la dépendance `@angular/core`.\n\nLe paramétrage `{ singleton: true, strictVersion: true, requiredVersion: 'auto' }` indique que les dépendances du \"remote1\" et du \"shell\" doivent être compatibles.\nDans le cas contraire, une erreur sera levée.\n\nNotez que l'application \"remote1\" pouvant être compilée de manière autonome, la dépendance `@angular/core` reste bien présente dans le bundle classique `dist/remote1/main.3a762787fa7157be.js`.\n\n## Le manifest\n\nLa schematic a également créé un manifest accessible à l'exécution (at runtime): `src/assets/mf.manifest.json`.\nCelui-ci indique sans ambiguïté où aller récupérer le `remoteEntry.js` du \"remote1\" :\n\n```json\n{\n  \"remote1\": \"http://localhost:4201/remoteEntry.js\"\n}\n```\n\nLe contenu du script `src/main.ts` a été déplacé dans `src/bootstrap.ts` :\n\n```ts\n// src/bootstrap.ts\nimport { bootstrapApplication } from '@angular/platform-browser';\nimport { provideRouter } from '@angular/router';\nimport { AppComponent } from './app/app.component';\nimport APP_ROUTES from './app/app.routes';\n\nbootstrapApplication(AppComponent, {\n  providers: [provideRouter(APP_ROUTES)],\n}).catch((err) =\u003e console.error(err));\n```\n\nEt désormais, l'application est chargée dynamiquement après que le manifest et ses différents `remoteEntry.js` ont bien été téléchargés :\n\n```ts\n// src/main.ts\nimport { initFederation } from '@angular-architects/module-federation';\n\ninitFederation('/assets/mf.manifest.json')\n  .catch((err) =\u003e console.error(err))\n  .then(() =\u003e import('./bootstrap'))\n  .catch((err) =\u003e console.error(err));\n```\n\nLa fonction `initFederation` télécharge donc le manifest et met à disposition du \"shell\" les différents `remoteEntry.js`.\n\n## La navigation vers le Micro Frontend\n\nNous y sommes presque ! Il ne nous reste plus qu'à naviguer vers le Micro Frontend depuis le \"shell\" grâce à une simple `Route` Angular :\n\n```ts\n// src/app/app.routes.ts\nimport { loadRemoteModule } from '@angular-architects/module-federation';\nimport { Routes } from '@angular/router';\nimport { HomeComponent } from './home.component';\n\nconst APP_ROUTES: Routes = [\n  ...,\n  {\n    path: 'remote1',\n    loadChildren: () =\u003e\n      loadRemoteModule({\n        type: 'manifest',\n\n        // \"remote1\" correspond à la clé dans le manifest `src/assets/mf.manifest.json`\n        remoteName: 'remote1',\n\n        // \"Routes\" correspond à la clé du symbole exposé dans `projects/remote1/webpack.config.js`\n        exposedModule: './Routes',\n      }),\n  },\n];\n\nexport default APP_ROUTES;\n```\n\nEt voilà, si nous ouvrons l'URL `http://localhost:4200/remote1`, nous visualisons bien le \"remote1\" à l'intérieur du \"shell\".\n\n```html\n\u003cp\u003eData exposed to Remote 1: { \"value\": \"Hello Micro Frontend\" }\u003c/p\u003e\n```\n\nLa donnée initialisée par le \"shell\" est bien celle qui est affichée dans le \"remote1\".\nCela prouve que le `SharedService` est bien un singleton pour le \"shell\" et le \"remote1\" !\n\n## Conclusion\n\nDans cet article, vous avez découvert une manière d'implémenter les Micro Frontends dans Angular.\nEt grâce au [module-federation-plugin](https://github.com/angular-architects/module-federation-plugin), l'opération ne nécessite que quelques fichiers de configuration.\nVoilà, j'espère que cet article vous a plu et qu'il vous a ouvert de nouveaux horizons pour architecturer vos projets Angular.\n\n## Références\n\n- https://www.angulararchitects.io/en/aktuelles/the-microfrontend-revolution-module-federation-in-webpack-5/\n- https://github.com/angular-architects/module-federation-plugin\n","project_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Favine%2Fgeekle-angular-global-summit-2023","html_url":"https://awesome.ecosyste.ms/projects/github.com%2Favine%2Fgeekle-angular-global-summit-2023","lists_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Favine%2Fgeekle-angular-global-summit-2023/lists"}