En este blog, explicaremos cómo modificar la aplicación shell para cargar varios microfrontends uno al lado del otro.

El objetivo: una página compuesta


En lugar de navegar entre diferentes microfrontends, crearemos una página compuesta que renderice ambos microfrontends al mismo tiempo. Nuestro diseño final tendrá este aspecto:

Aplicación host (aplicación shell)

🔹 Microfrontend 1 (MF1): renderizado en una sección
🔹 Microfrontend 2 (MF2): renderizado en otra sección


Configuración de la página compuesta


Comencemos creando un componente de página compuesta dentro de la aplicación shell. Esto servirá como contenedor para cargar ambos micro frontends dinámicamente.

Crear la plantilla de página compuesta
Modifique el archivo composite-page.component.html para renderizar ambos micro frontends en secciones separadas:

<div class="micro-frontend-container">
  <div class="micro-frontend">
    <!-- Render Micro Frontend 1 -->
    <ng-container *ngIf="mf1Component" [ngComponentOutlet]="mf1Component"></ng-container>
  </div>
  <hr>
  <div class="micro-frontend">
    <!-- Render Micro Frontend 2 -->
    <ng-container *ngIf="mf2Component" [ngComponentOutlet]="mf2Component"></ng-container>
  </div>
</div>

Cargar ambos microfrontends de forma dinámica


En el archivo composite-page.component.ts, utilice loadRemoteModule para obtener y representar ambos microfrontends:

import { Component, OnInit } from '@angular/core';
import { loadRemoteModule } from '@angular-architects/native-federation';
import { CommonModule } from '@angular/common';
import { RouterModule } from '@angular/router';

@Component({
  selector: 'app-composite-page',
  standalone: true,
  imports: [CommonModule, RouterModule],
  templateUrl: './composite-page.component.html',
  styleUrl: './composite-page.component.css'
})
export class CompositePageComponent implements OnInit {
  public mf1Component: any;
  public mf2Component: any;

  async ngOnInit() {
    // Load Micro Frontend 1
    const mf1 = await loadRemoteModule({
      remoteName: 'micro-frontend-1',
      exposedModule: './Component'
    });
    this.mf1Component = mf1.AppComponent;

    // Load Micro Frontend 2
    const mf2 = await loadRemoteModule({
      remoteName: 'micro-frontend-2',
      exposedModule: './Component'
    });
    this.mf2Component = mf2.AppComponent;
  }
}

Esto garantiza que ambos microfrontends se carguen dinámicamente sin navegación.

Ahora, modifique el archivo app.routes.ts para establecer la página compuesta como la ruta predeterminada:

import { loadRemoteModule } from '@angular-architects/native-federation';
import { Routes } from '@angular/router';
import { CompositePageComponent } from './composite-page/composite-page.component';

export const routes: Routes = [
    {
        path: '',
        component: CompositePageComponent
    },
    {
        path: 'micro-frontend-1',
        loadComponent: () => loadRemoteModule('micro-frontend-1', './Component').then((m) => m.AppComponent)
    },
    {
        path: 'micro-frontend-2',
        loadComponent: () => loadRemoteModule('micro-frontend-2', './Component').then((m) => m.AppComponent)
    }
];

Ahora, cuando se cargue la aplicación, se mostrarán ambos micro frontends de forma predeterminada.

Actualización de la navegación

Para mejorar la navegación, actualice app.component.html para incluir un botón de inicio:

<div class="menu">
  <a routerLink="/home" routerLinkActive="active">Home</a>
  <a routerLink="/micro-frontend-1" routerLinkActive="active">MF1</a>
  <a routerLink="/micro-frontend-2" routerLinkActive="active">MF2</a>
</div>
<div class="container">
  <router-outlet></router-outlet>
</div>

Ahora, al hacer clic en «Inicio», se mostrarán ambos microinterfaces uno al lado del otro.

Ejecución de la aplicación
Inicie la aplicación de shell y ambos microinterfaces:

# Start the host application
cd host-app
ng serve

# Start the first micro frontend
cd ../micro-frontend-1
ng serve --port 4201

# Start the second micro frontend
cd ../micro-frontend-2
ng serve --port 4202

Una vez en funcionamiento, navega a http://localhost:4200, ¡y deberías ver ambos micro frontends mostrados en la misma página! 🎉

Error común: NG0912: colisión en la generación de ID de componente


Pero después de implementar esta configuración, puede encontrar el siguiente error:

¿Por qué ocurre esto?


Tanto micro-frontend-1 como micro-frontend-2 definen su AppComponent utilizando el selector predeterminado:

selector: 'app-root'

Cuando el shell intenta representar ambos MFs al mismo tiempo, Angular detecta dos componentes con el mismo selector, lo que provoca un conflicto.
Solución: utilizar selectores únicos en microfrontends
Para resolver esto, cambie el selector en el AppComponent de cada microfrontend.

micro-frontend-1/src/app/app.component.ts

@Component({
  selector: 'mf1-root', // Change to a unique selector
  templateUrl: './app.component.html',
  styleUrls: ['./app.component.css']
})
export class AppComponent { }

micro-frontend-2/src/app/app.component.ts

@Component({
  selector: 'mf2-root', // Change to a unique selector
  templateUrl: './app.component.html',
  styleUrls: ['./app.component.css']
})
export class AppComponent { }

Al utilizar selectores únicos para cada microfrontend, evitamos colisiones de ID de componentes, lo que garantiza una representación fluida y una integración perfecta dentro de la aplicación shell.

Conclusión
Al implementar una página compuesta, tenemos varios microfrontends a la vez sin necesidad de navegación. Este enfoque es útil para:

✔️ Paneles: mostrar varios widgets de diferentes microfrontends.
✔️ Vistas de datos de múltiples fuentes: combinar datos de varios equipos o servicios.
✔️ UI/UX mejorada: reducción de la navegación innecesaria para los usuarios finales.
Fuente