Enterprise Angular

Microfrontends


The Goal

Microfrontends (MFEs) make it possible for large teams to indepently develop and deploy features in isolation.

We will discuss how MFEs work with module federation and the difference between static and dynamic federation.

Our goal is to wire up our host application to load in our remote features as MFEs.

The Commands

Create a dashboard host application and remote applications called users, challenges, flashcards, and notes.

npx nx g @nx/angular:host dashboard \
  --style=scss \
  --dynamic=true \
  --e2eTestRunner=cypress \
  --remotes=users,challenges,flashcards,notes \
  --projectNameAndRootFormat=derived \
  --ssr=false

The Code

import { ModuleFederationConfig } from '@nx/webpack';

const config: ModuleFederationConfig = {
  name: 'challenges',
  exposes: {
    './Routes': 'apps/challenges/src/app/remote-entry/entry.routes.ts',
  },
};

export default config;
import { ModuleFederationConfig } from '@nx/webpack';

const config: ModuleFederationConfig = {
  name: 'dashboard',
  remotes: [],
};

export default config;
import { setRemoteDefinitions } from '@nx/angular/mf';

fetch('/assets/module-federation.manifest.json')
  .then((res) => res.json())
  .then((definitions) => setRemoteDefinitions(definitions))
  .then(() => import('./bootstrap').catch((err) => console.error(err)));
export const appRoutes: Route[] = [
  {
    path: 'notes',
    loadChildren: () =>
      loadRemoteModule('notes', './Routes').then((m) => m.remoteRoutes),
  },
  {
    path: 'flashcards',
    loadChildren: () =>
      loadRemoteModule('flashcards', './Routes').then((m) => m.remoteRoutes),
  },
  {
    path: 'challenges',
    loadChildren: () =>
      loadRemoteModule('challenges', './Routes').then((m) => m.remoteRoutes),
  },
  {
    path: 'users',
    loadChildren: () =>
      loadRemoteModule('users', './Routes').then((m) => m.remoteRoutes),
  },
  {
    path: '',
    component: HomeComponent,
  },
];

Remote

import { bootstrapApplication } from '@angular/platform-browser';
import { appConfig } from './app/app.config';
import { RemoteEntryComponent } from './app/remote-entry/entry.component';

bootstrapApplication(RemoteEntryComponent, appConfig).catch((err) =>
  console.error(err)
);
import { ModuleFederationConfig } from '@nx/webpack';

const config: ModuleFederationConfig = {
  name: 'challenges',
  exposes: {
    './Routes': 'apps/challenges/src/app/remote-entry/entry.routes.ts',
  },
};

export default config;

Static to Dynamic

// apps/dashboard/src/assets/module-federation.manifest.json
{
  "challenges": "http://localhost:4201",
  "flashcards": "http://localhost:4202"
}
// apps/dashboard/src/main.ts
import { setRemoteDefinitions } from '@nx/angular/mf';

fetch('/assets/module-federation.manifest.json')
  .then((res) => res.json())
  .then((definitions) => setRemoteDefinitions(definitions))
  .then(() => import('./bootstrap').catch((err) => console.error(err)));
// apps/dashboard/module-federation.config.ts
import { ModuleFederationConfig } from '@nx/webpack';

const config: ModuleFederationConfig = {
  name: 'dashboard',
  remotes: [],
};

export default config;
// apps/dashboard/src/app/app.routes.ts
import { NxWelcomeComponent } from './nx-welcome.component';
import { Route } from '@angular/router';
import { loadRemoteModule } from '@nx/angular/mf';

export const appRoutes: Route[] = [
  {
    path: 'flashcards',
    loadChildren: () =>
      loadRemoteModule('flashcards', './Routes').then((m) => m.remoteRoutes),
  },
  {
    path: 'challenges',
    loadChildren: () =>
      loadRemoteModule('challenges', './Routes').then((m) => m.remoteRoutes),
  },
  {
    path: '',
    component: NxWelcomeComponent,
  },
];

Challenges

Generate the

< Angular Home