如何在Angular中通过Native Federation动态加载远程组件并使用其选择器?
我有一个运行在端口4202的微前端(products仓库),该仓库的联邦配置文件federation.config.js如下:
const { withNativeFederation, shareAll } = require('@angular-architects/native-federation/config'); module.exports = withNativeFederation({ name: 'mfe2', exposes: { './SelectorLoaded': './src/app/selector-loaded/selector-loaded.component.ts', // other exposed components }, // additional configuration });
我希望从mfe2动态加载SelectorLoaded组件,在宿主应用的父组件中直接使用其选择器,并无缝向该组件传递输入、接收其输出。
为此我实现了如下方法:
loadMicroFrontend3() { loadRemoteModule('mfe2', './SelectorLoaded') .then((MicroModule: any) => { const ce = createCustomElement(MicroModule.SelectorLoadedComponent, { injector: this.injector, }); if (!customElements.get('app-selector-loaded')) { customElements.define('app-selector-loaded', ce); } }) .catch((err: any) => console.error('Error loading microfrontend:', err)); }
但该方法无效,尽管我可以通过如下路由配置成功加载并渲染SelectorLoaded组件:
{ path: 'stepper', loadComponent: () => loadRemoteModule('mfe2', './SelectorLoaded').then((m) => m.SelectorLoadedComponent), }
// federation.manifest.json { "mfe1": "http://localhost:4201/remoteEntry.json", "mfe2": "http://localhost:4202/remoteEntry.json" }
我无法在宿主应用的组件中使用SelectorLoadedComponent的选择器(如<app-selector-loaded></app-selector-loaded>)。此前我通过Module Federation实现了该功能,但Native Federation下无法实现。
我的问题:
- 是否可通过Native Federation动态加载暴露的微前端组件,实现以下需求:
- 在宿主应用中直接使用其选择器?
- 像本地Angular组件一样传递输入、监听输出?
- 若不可行,Native Federation下实现该功能的推荐方式是什么?
问题1解答:可以实现,但当前方式存在适配问题
Native Federation支持动态加载组件并通过选择器使用,同时支持输入输出传递,但你用createCustomElement的方式不匹配Native Federation的组件暴露逻辑——它暴露的是Angular组件类,而createCustomElement面向Web Component规范,直接转换会因依赖共享、模块上下文冲突失效。
要实现目标需求,需调整加载逻辑,确保组件被正确注册到宿主的Angular运行环境中:
实现方案1:通过容器动态挂载组件(推荐)
不用转成Web Component,直接通过ViewContainerRef创建远程组件实例,同时传递输入、监听输出:
import { Component, ViewChild, ViewContainerRef, Injector } from '@angular/core'; import { loadRemoteModule } from '@angular-architects/native-federation'; @Component({ selector: 'app-host-parent', template: ` <button (click)="loadMicroFrontend3()">加载组件</button> <div #componentContainer></div> ` }) export class HostParentComponent { @ViewChild('componentContainer', { read: ViewContainerRef }) container!: ViewContainerRef; constructor(private injector: Injector) {} async loadMicroFrontend3() { try { const remoteModule = await loadRemoteModule('mfe2', './SelectorLoaded'); const ComponentClass = remoteModule.SelectorLoadedComponent; // 创建组件实例并挂载到容器 const componentRef = this.container.createComponent(ComponentClass, { injector: this.injector }); // 传递输入属性 componentRef.instance.yourInputProp = '测试输入值'; // 监听输出事件 componentRef.instance.yourOutputEvent.subscribe((data: any) => { console.log('收到子组件输出:', data); }); } catch (err) { console.error('加载微前端失败:', err); } } }
实现方案2:动态注册组件以直接使用选择器
若要在模板中直接写<app-selector-loaded>,需动态创建NgModule并注册远程组件:
import { NgModule, Compiler } from '@angular/core'; import { loadRemoteModule } from '@angular-architects/native-federation'; constructor(private compiler: Compiler, private injector: Injector) {} async loadAndRegisterComponent() { const remoteModule = await loadRemoteModule('mfe2', './SelectorLoaded'); const ComponentClass = remoteModule.SelectorLoadedComponent; // 动态创建包含该组件的模块 const dynamicModule = NgModule({ declarations: [ComponentClass], exports: [ComponentClass] })(class {}); // 编译并加载模块 const moduleFactory = await this.compiler.compileModuleAsync(dynamicModule); moduleFactory.create(this.injector); // 此时可在模板中直接使用<app-selector-loaded>标签 }
注意:这种方式要求宿主与远程应用的Angular版本、共享依赖完全兼容,否则会出现编译或运行时错误。
问题2解答:Native Federation的推荐方式
如果上述动态注册方式觉得复杂,官方推荐两种更稳定的集成方案:
- 路由级加载:即你已实现的通过
loadComponent在路由中加载组件,适合页面级的微前端集成,无需额外处理输入输出,路由参数可通过激活路由传递。 - 容器式组件加载:即上述方案1的方式,通过
ViewContainerRef动态挂载组件,既能满足在父组件中直接使用的需求,又能无缝传递输入输出,是组件级集成的最优方案。
不推荐用Web Component封装Native Federation组件,两者的依赖注入、变更检测机制存在冲突,容易引发难以排查的问题。
内容的提问来源于stack exchange,提问作者mideo mideo

