# Advanced Angular Micro Frontends with Dynamic Module Federation Dynamic Module Federation is a technique that allows an application to determine the location of its remote applications at runtime. It helps to achieve the use case of **“Build once, deploy everywhere”**. “Build once, deploy everywhere” is the concept of being able to create a single build artifact of your application and deploy it to multiple environments such as staging and production. The difficulty in achieving this with a Micro Frontend Architecture using Static Module Federation is that our Remote applications will have a different location (or URL) in each environment. Previously, to account for this, we would have had to specify the deployed location of the Remote applications and rebuild the application for the target environment. This guide will walk through how the concept of “Build once, deploy everywhere” can be easily achieved in a Micro Frontend Architecture that uses Dynamic Module Federation. ## Aim The aim of this guide is three-fold. We want to be able to: - Set up a Micro Frontend with Static Module Federation - Transform an existing Static Module Federation setup to use Dynamic Federation - Generate a new Micro Frontend application that uses Dynamic Federation ## What we’ll build To achieve the aims, we will do the following: - Create a Static Federation Micro Frontend Architecture - Change the **Dashboard** application to use Dynamic Federation - Generate a new **Employee Dashboard** application that will use Dynamic Federation - It should use the existing **Login** application. - It should use a new **Todo** application. ## Final Code Here's the source code of the final result for this guide. {% github-repository url="https://github.com/Coly010/nx-ng-dyn-fed" /%} ## First steps ### Create an Nx Workspace To start with, we need to create a new Nx Workspace. We can do this easily with: {% tabs %} {% tab label="npm" %} ```shell npx create-nx-workspace ng-mf ``` {% /tab %} {% tab label="yarn" %} ```shell yarn create nx-workspace ng-mf ``` {% /tab %} {% tab label="pnpm" %} ```shell pnpm create nx-workspace ng-mf ``` {% /tab %} {% /tabs %} You'll be prompted a few questions. Pick the `Angular` stack, `Integrated Monorepo` layout and the `webpack` bundler. You can use the default values for the rest of the prompts. We won't use the application that gets generated by default on this guide, so you can remove it. ### Creating our applications We need to generate two applications that support Module Federation. We'll start with the **Admin Dashboard** application which will act as a host application for the Micro-Frontends (_MFEs_): ```shell nx g @nx/angular:host apps/dashboard --prefix=ng-mf ``` {% callout type="note" title="Running nx commands" %} The terminal examples in this guide will show `nx` being run as if it is installed globally. If you have not installed Nx globally (not required), you can use your package manager to run the `nx` local binary: - NPM: `npx nx ...` - Yarn: `yarn nx ...` - PNPM: `pnpm nx ...` {% /callout %} The `host` generator will create and modify the files needed to set up the Angular application. Now, let's generate the **Login** application as a remote application that will be consumed by the **Dashboard** host application. ```shell nx g @nx/angular:remote apps/login --prefix=ng-mf --host=dashboard ``` Note how we provided the `--host=dashboard` option. This tells the generator that this remote application will be consumed by the **Dashboard** application. The generator performed the following changes to automatically link these two applications together: - Added the remote to the `apps/dashboard/module-federation.config.ts` file - Added a TypeScript path mapping to the root tsconfig file - Added a new route to the `apps/dashboard/src/app/app.routes.ts` file ## What was generated? Let's take a closer look after generating each application. For both applications, the generators did the following: - Created the standard Angular application files - Added a `module-federation.config.ts` file - Added a `webpack.config.ts` and `webpack.prod.config.ts` - Added a `src/bootstrap.ts` file - Moved the code that is normally in `src/main.ts` to `src/bootstrap.ts` - Changed `src/main.ts` to dynamically import `src/bootstrap.ts` _(this is required for the Module Federation to load versions of shared libraries correctly)_ - Updated the `build` target in the `project.json` to use the `@nx/angular:webpack-browser` executor _(this is required to support passing a custom Webpack configuration to the Angular compiler)_ - Updated the `serve` target to use `@nx/angular:dev-server` _(this is required as we first need Webpack to build the application with our custom Webpack configuration)_ The key differences reside within the configuration of the Module Federation Plugin within each application's `module-federation.config.ts`. We can see the following in the **Login** micro frontend configuration: ```ts {% fileName="apps/login/module-federation.config.ts" %} import { ModuleFederationConfig } from '@nx/webpack'; const config: ModuleFederationConfig = { name: 'login', exposes: { './Routes': 'apps/login/src/app/remote-entry/entry.routes.ts', }, }; export default config; ``` Taking a look at each property of the configuration in turn: - `name` is the name that Webpack assigns to the remote application. It **must** match the name of the project. - `exposes` is the list of source files that the remote application exposes to consuming shell applications for their own use. This config is then used in the `webpack.config.ts` file: ```ts {% fileName="apps/login/webpack.config.ts" %} import { withModuleFederation } from '@nx/angular/module-federation'; import config from './module-federation.config'; export default withModuleFederation(config); ``` We can see the following in the **Dashboard** micro frontend configuration: ```ts {% fileName="apps/dashboard/module-federation.config.ts" %} import { ModuleFederationConfig } from '@nx/webpack'; const config: ModuleFederationConfig = { name: 'dashboard', remotes: ['login'], }; export default config; ``` The key difference to note with the **Dashboard** configuration is the `remotes` array. This is where you list the remote applications you want to consume in your host application. You give it a name that you can reference in your code, in this case `login`. Nx will find where it is served. Now that we have our applications generated, let's move on to building out some functionality for each. ## Adding Functionality We'll start by building the **Login** application, which will consist of a login form and some very basic and insecure authorization logic. ### User Library Let's create a user data-access library that will be shared between the host application and the remote application. This will be used to determine if there is an authenticated user as well as providing logic for authenticating the user. ```shell nx g @nx/angular:lib libs/shared/data-access-user ``` This will scaffold a new library for us to use. We need an Angular Service that we will use to hold state: ```shell nx g @nx/angular:service user --project=data-access-user ``` This will create the `libs/shared/data-access-user/src/lib/user.service.ts` file. Change its contents to match: ```ts {% fileName="libs/shared/data-access-user/src/lib/user.service.ts" %} import { Injectable } from '@angular/core'; import { BehaviorSubject } from 'rxjs'; @Injectable({ providedIn: 'root' }) export class UserService { private isUserLoggedIn = new BehaviorSubject(false); isUserLoggedIn$ = this.isUserLoggedIn.asObservable(); checkCredentials(username: string, password: string) { if (username === 'demo' && password === 'demo') { this.isUserLoggedIn.next(true); } } logout() { this.isUserLoggedIn.next(false); } } ``` Now, export the service in the library's entry point file: ```ts {% fileName="libs/shared/data-access-user/src/index.ts" %} ... export * from './lib/user.service'; ``` ### Login Application Let's set up our `entry.component.ts` file in the **Login** application so that it renders a login form. We'll import `FormsModule` and inject our `UserService` to allow us to sign the user in: ```ts {% fileName="apps/login/src/app/remote-entry/entry.component.ts" %} import { Component } from '@angular/core'; import { CommonModule } from '@angular/common'; import { FormsModule } from '@angular/forms'; import { UserService } from '@ng-mf/data-access-user'; @Component({ standalone: true, imports: [CommonModule, FormsModule], selector: 'ng-mf-login-entry', template: `