AngularMicro-FrontendsModule FederationStandalone ComponentsArchitecture

Architecting Micro-Frontends with Angular Standalone Components

E
Enterprise Architect
Featured Guide 28 min read

Death to the NgModule.

Micro-frontends used to be a nightmare of shared modules, scope collisions, and "entry components."

With Standalone Components, a micro-frontend is just a URL that exports a single Component class. No modules. No boilerplate.

Deep Dive: Version Skew Hell

Using MFEs doesn't magically solve dependency issues. If Host uses Angular 18 and Remote uses Angular 16, they might both try to load different versions of zone.js or rxjs.

Rule of Thumb: Force all MFEs to use the exact same version of Core Framework dependencies via a Monorepo policy.

02. Simplified Config

Using modern builders (like native federation or Nx), exposing a standalone component is trivial.

// payment-mfe/federation.config.js
export default
{'{'}
  name: 'payment',
  exposes: {'{'}
    './Component': './src/app/payment/payment.component.ts'
  {'}'}
{'}'}

03. Communication Strategy

Don't: Shared Service

Sharing a singleton state service between MFEs couples them during build time and can lead to version mismatches.

Do: Custom Events / Signals

Use DOM Custom Events or a lightweight PubSub (window Signal) to dispatch actions like "PAYMENT_SUCCESS" that the Host listens to.

04. The Senior Engineer's Take

You probably don't need MFEs.

Micro-frontends introduce massive operational complexity (deployment coordination, versioning).

Only use this pattern if: You have multiple autonomous teams (20+ devs) effectively deploying to the same page separately. If you are one team, use a Modulith (Monorepo).

Interactive Playground

import React, { useState } from 'react';

// 🧊 Micro-Frontend Architect

export default function StandaloneMFEDemo() {
    const [selectedApp, setSelectedApp] = useState('host');

    return (
        <div className="bg-slate-50 dark:bg-slate-950 p-8 rounded-3xl border border-slate-200 dark:border-slate-800 shadow-xl">
            <h3 className="text-3xl font-black text-gray-900 dark:text-white mb-8 flex items-center gap-3">
                <span className="text-indigo-500">🧊</span> Federation Dashboard
            </h3>

            <div className="flex flex-col lg:flex-row gap-8">
            
                {/* Visual Architecture */}
                <div className="flex-1 bg-white dark:bg-slate-900 rounded-2xl p-8 border border-slate-200 dark:border-slate-800 relative">
                     <div className="text-xs font-bold text-gray-400 uppercase mb-4 text-center">Browser Window</div>
                     
                     <div className="border-4 border-dashed border-gray-300 dark:border-slate-700 rounded-xl p-8 min-h-[400px] relative">
                         <div className="absolute top-0 left-0 bg-gray-200 dark:bg-slate-800 px-4 py-1 text-xs font-bold rounded-br-xl text-gray-600 dark:text-gray-300">
                             HOST APP (Shell)
                         </div>
                         
                         <div className="mt-8 grid grid-cols-2 gap-8 h-full">
                             <div className="col-span-2 bg-blue-50 dark:bg-blue-900/20 p-4 rounded-xl border border-blue-100 dark:border-blue-900/50 flex items-center justify-center h-24">
                                 <span className="font-bold text-blue-600 dark:text-blue-400">Host Navigation</span>
                             </div>

                             <div 
                                className="group relative bg-green-50 dark:bg-green-900/20 p-4 rounded-xl border border-green-100 dark:border-green-900/50 min-h-[200px] flex items-center justify-center cursor-pointer hover:shadow-lg transition-all"
                                onClick={() => setSelectedApp('products')}
                             >
                                 <div className="text-center">
                                     <span className="text-4xl mx-auto mb-2 block">🛍️</span>
                                     <div className="font-bold text-green-700 dark:text-green-400">Product MFE</div>
                                     <div className="text-xs text-green-600/60 mt-2">localhost:4201</div>
                                 </div>
                                 {selectedApp === 'products' && <div className="absolute inset-0 border-2 border-green-500 rounded-xl pointer-events-none"></div>}
                             </div>

                             <div 
                                className="group relative bg-purple-50 dark:bg-purple-900/20 p-4 rounded-xl border border-purple-100 dark:border-purple-900/50 min-h-[200px] flex items-center justify-center cursor-pointer hover:shadow-lg transition-all"
                                onClick={() => setSelectedApp('payment')}
                             >
                                 <div className="text-center">
                                     <span className="text-4xl mx-auto mb-2 block">💳</span>
                                     <div className="font-bold text-purple-700 dark:text-purple-400">Payment MFE</div>
                                     <div className="text-xs text-purple-600/60 mt-2">localhost:4202</div>
                                 </div>
                                 {selectedApp === 'payment' && <div className="absolute inset-0 border-2 border-purple-500 rounded-xl pointer-events-none"></div>}
                             </div>
                         </div>
                     </div>
                </div>

                {/* Config Panel */}
                <div className="w-full lg:w-1/3 bg-slate-900 rounded-2xl p-6 text-slate-300 font-mono text-sm border border-slate-800 flex flex-col">
                    <div className="flex items-center gap-2 mb-4 pb-4 border-b border-slate-700">
                        <span>⚙️</span>
                        <span className="font-bold">App Config</span>
                    </div>
                    
                    {selectedApp === 'host' && (
                        <div>
                            <div className="text-gray-500 mb-2">// Select a Micro-Frontend to view its Standalone Config</div>
                            <div className="text-center mt-10 opacity-50">Click blocks on left</div>
                        </div>
                    )}

                    {selectedApp === 'products' && (
                        <div className="animate-in fade-in slide-in-from-right-4">
                            <div className="text-green-400 mb-4 font-bold">Product MFE Config</div>
                            <span className="text-purple-400">@Component</span>({'{'} <br/>
                            &nbsp;&nbsp;standalone: <span className="text-yellow-400">true</span>,<br/>
                            &nbsp;&nbsp;selector: <span className="text-green-400">'product-list'</span>,<br/>
                            &nbsp;&nbsp;imports: [CommonModule] <br/>
                            {'}'}) <br/>
                            <span className="text-blue-400">export class</span> ProductListComponent {'{}'}
                            
                            <div className="mt-8 pt-4 border-t border-slate-800">
                                <span className="text-gray-500">// Exposed as remote</span><br/>
                                exposedModule: <span className="text-green-400">'./Component'</span>
                            </div>
                        </div>
                    )}
                    
                    {selectedApp === 'payment' && (
                        <div className="animate-in fade-in slide-in-from-right-4">
                            <div className="text-purple-400 mb-4 font-bold">Payment MFE Config</div>
                            <span className="text-purple-400">@Component</span>({'{'} <br/>
                            &nbsp;&nbsp;standalone: <span className="text-yellow-400">true</span>,<br/>
                            &nbsp;&nbsp;selector: <span className="text-green-400">'payment-widget'</span>,<br/>
                            &nbsp;&nbsp;imports: [StripeModule] <br/>
                            {'}'}) <br/>
                            <span className="text-blue-400">export class</span> PaymentComponent {'{}'}

                             <div className="mt-8 pt-4 border-t border-slate-800">
                                <span className="text-gray-500">// Exposed as remote</span><br/>
                                exposedModule: <span className="text-green-400">'./Widget'</span>
                            </div>
                        </div>
                    )}

                </div>
            </div>
        </div>
    );
}