在查看这篇文章之前,可以先简单预览一下这篇基础版的博客:
MFE微前端基础版:Angular + Module Federation + webpack + 路由(Route way)完整示例 -CSDN博客
这篇Angular + Module Federation 高级路由配置详解包括嵌套路由、路由守卫、懒加载策略和动态路由等高级功能。
import { NgModule } from '@angular/core';
import { RouterModule, Routes, UrlSegment } from '@angular/router';
import { AuthGuard } from './core/guards/auth.guard';
import { MfePreloadStrategy } from './core/strategies/mfe-preload.strategy';
// 动态路由匹配函数 - 用于识别微前端路由
export function mfe1Matcher(url: UrlSegment[]) {
return url.length > 0 && url[0].path.startsWith('mfe1-') ?
{ consumed: [url[0]] } : null;
}
export function mfe2Matcher(url: UrlSegment[]) {
return url.length > 0 && url[0].path.startsWith('mfe2-') ?
{ consumed: [url[0]] } : null;
}
const routes: Routes = [
{
path: '',
pathMatch: 'full',
redirectTo: 'dashboard'
},
{
path: 'dashboard',
loadChildren: () => import('./dashboard/dashboard.module').then(m => m.DashboardModule),
canActivate: [AuthGuard]
},
// 标准微前端路由配置
{
path: 'mfe1',
loadChildren: () => import('mfe1/Module').then(m => m.Mfe1Module),
data: { preload: true, mfe: 'mfe1' }
},
{
path: 'mfe2',
loadChildren: () => import('mfe2/Module').then(m => m.Mfe2Module),
canLoad: [AuthGuard],
data: { mfe: 'mfe2' }
},
// 动态微前端路由配置 - 使用自定义URL匹配器
{
matcher: mfe1Matcher,
loadChildren: () => import('mfe1/DynamicModule').then(m => m.DynamicModule),
data: { dynamic: true }
},
{
matcher: mfe2Matcher,
loadChildren: () => import('mfe2/DynamicModule').then(m => m.DynamicModule),
data: { dynamic: true }
},
// 通配符路由 - 捕获所有未匹配的路由
{
path: '**',
loadChildren: () => import('./not-found/not-found.module').then(m => m.NotFoundModule)
}
];
@NgModule({
imports: [RouterModule.forRoot(routes, {
preloadingStrategy: MfePreloadStrategy, // 自定义预加载策略
paramsInheritanceStrategy: 'always', // 始终继承路由参数
enableTracing: false // 生产环境应设为false
})],
exports: [RouterModule],
providers: [MfePreloadStrategy]
})
export class AppRoutingModule { }
import { PreloadingStrategy, Route } from '@angular/router';
import { Observable, of } from 'rxjs';
import { MfeLoaderService } from '../services/mfe-loader.service';
@Injectable({ providedIn: 'root' })
export class MfePreloadStrategy implements PreloadingStrategy {
constructor(private mfeLoader: MfeLoaderService) {}
preload(route: Route, load: () => Observable): Observable {
// 根据路由数据决定是否预加载
if (route.data?.['preload'] && route.data?.['mfe']) {
// 预加载微前端应用
this.mfeLoader.preloadMfe(route.data['mfe']);
return load();
}
return of(null);
}
}
import { Injectable } from '@angular/core';
import { LoadRemoteModuleOptions } from '@angular-architects/module-federation';
@Injectable({ providedIn: 'root' })
export class MfeLoaderService {
private loadedMfes = new Set();
preloadMfe(mfeName: string): void {
if (this.loadedMfes.has(mfeName)) return;
const options: LoadRemoteModuleOptions = {
type: 'module',
exposedModule: './Module'
};
switch (mfeName) {
case 'mfe1':
options.remoteEntry = 'http://localhost:4201/remoteEntry.js';
options.remoteName = 'mfe1';
break;
case 'mfe2':
options.remoteEntry = 'http://localhost:4202/remoteEntry.js';
options.remoteName = 'mfe2';
break;
}
import('@angular-architects/module-federation').then(m => {
m.loadRemoteModule(options).then(() => {
this.loadedMfes.add(mfeName);
console.log(`${mfeName} preloaded`);
});
});
}
}
import { NgModule } from '@angular/core';
import { RouterModule, Routes } from '@angular/router';
import { Mfe1HomeComponent } from './components/home/home.component';
import { Mfe1DetailComponent } from './components/detail/detail.component';
import { Mfe1Guard } from './guards/mfe1.guard';
const routes: Routes = [
{
path: '',
component: Mfe1HomeComponent,
children: [
{
path: 'detail/:id',
component: Mfe1DetailComponent,
data: {
breadcrumb: 'Detail'
}
},
{
path: 'admin',
loadChildren: () => import('./admin/admin.module').then(m => m.AdminModule),
canLoad: [Mfe1Guard]
},
{
path: 'lazy',
loadChildren: () => import('./lazy/lazy.module').then(m => m.LazyModule)
}
]
},
// 动态模块暴露的路由
{
path: 'dynamic',
loadChildren: () => import('./dynamic/dynamic.module').then(m => m.DynamicModule)
}
];
@NgModule({
imports: [RouterModule.forChild(routes)],
exports: [RouterModule]
})
export class Mfe1RoutingModule { }
@NgModule({
imports: [
Mfe1RoutingModule,
// 其他模块...
],
declarations: [Mfe1HomeComponent, Mfe1DetailComponent]
})
export class Mfe1Module { }
import { Injectable } from '@angular/core';
import { Router, Routes } from '@angular/router';
@Injectable({ providedIn: 'root' })
export class DynamicRoutesService {
private dynamicRoutes: Routes = [];
constructor(private router: Router) {}
registerRoutes(newRoutes: Routes): void {
// 合并新路由
this.dynamicRoutes = [...this.dynamicRoutes, ...newRoutes];
// 重置路由配置
this.router.resetConfig([
...this.router.config,
...this.dynamicRoutes
]);
}
unregisterRoutes(routesToRemove: Routes): void {
this.dynamicRoutes = this.dynamicRoutes.filter(
route => !routesToRemove.includes(route)
);
this.router.resetConfig([
...this.router.config.filter(
route => !routesToRemove.includes(route)
),
...this.dynamicRoutes
]);
}
}
import { Injectable } from '@angular/core';
import { Router, NavigationEnd } from '@angular/router';
import { filter } from 'rxjs/operators';
@Injectable({ providedIn: 'root' })
export class RouteSyncService {
private currentRoute = '';
constructor(private router: Router) {
this.router.events.pipe(
filter(event => event instanceof NavigationEnd)
).subscribe((event: NavigationEnd) => {
this.currentRoute = event.url;
// 可以在这里广播路由变化给所有微前端
this.broadcastRouteChange(event.url);
});
}
private broadcastRouteChange(url: string): void {
// 使用自定义事件或状态管理库广播路由变化
window.dispatchEvent(new CustomEvent('microfrontend:route-change', {
detail: { url }
}));
}
syncRoute(mfeName: string): void {
// 微前端可以调用此方法来同步路由状态
this.router.navigateByUrl(this.currentRoute);
}
}
import { Injectable } from '@angular/core';
import { CanLoad, Route, UrlSegment, Router } from '@angular/router';
import { AuthService } from '../services/auth.service';
@Injectable({ providedIn: 'root' })
export class Mfe1Guard implements CanLoad {
constructor(
private authService: AuthService,
private router: Router
) {}
canLoad(route: Route, segments: UrlSegment[]): boolean {
const requiredRole = route.data?.['requiredRole'];
if (this.authService.hasRole(requiredRole)) {
return true;
}
// 重定向到主应用的未授权页面
this.router.navigate(['/unauthorized'], {
queryParams: { returnUrl: segments.join('/') }
});
return false;
}
}
import { Injectable } from '@angular/core';
import { Resolve, ActivatedRouteSnapshot, RouterStateSnapshot } from '@angular/router';
import { Observable } from 'rxjs';
import { UserService } from '../services/user.service';
@Injectable({ providedIn: 'root' })
export class UserResolver implements Resolve {
constructor(private userService: UserService) {}
resolve(route: ActivatedRouteSnapshot, state: RouterStateSnapshot): Observable {
const userId = route.paramMap.get('id');
return this.userService.getUser(userId);
}
}
示例配置:
const routes: Routes = [
{ path: '', component: HomeComponent }, // 空路径
{ path: 'login', component: LoginComponent }
];
http://localhost:4200
)时,Angular 会优先匹配 path: ''
的路由规则。redirectTo
,但定义了空路径对应的组件(如 HomeComponent
),则会直接渲染该组件。规则:Angular 按顺序匹配路由配置,第一个匹配的规则生效。
示例:
const routes: Routes = [
{ path: 'dashboard', component: DashboardComponent },
{ path: ':id', component: UserComponent } // 动态参数路由
];
现象:
访问 /dashboard
会匹配第一个路由,渲染 DashboardComponent
。
访问 /123
会匹配第二个路由,渲染 UserComponent
。
即使没有 redirectTo
,也能直接定位到组件。
示例配置:
const routes: Routes = [
{
path: 'admin',
component: AdminComponent,
children: [
{ path: '', component: AdminDashboardComponent }, // 子路由的空路径
{ path: 'users', component: UserListComponent }
]
}
];
现象:
访问 /admin
时,会渲染 AdminComponent
的模板,并在
中显示 AdminDashboardComponent
(因为子路由的空路径匹配)。
虽然未显式配置 redirectTo
,但子路由的空路径规则会直接加载组件。
**
) 的兜底行为示例配置:
const routes: Routes = [
{ path: 'home', component: HomeComponent },
{ path: '**', component: NotFoundComponent } // 通配符路由
];
现象:
访问未定义的路径(如 /foo
)时,会直接匹配 **
规则,渲染 NotFoundComponent
。
无需 redirectTo
,通配符路由会直接显示组件。
懒加载模块的默认行为:
const routes: Routes = [
{ path: 'lazy', loadChildren: () => import('./lazy/lazy.module').then(m => m.LazyModule) }
];
如果 LazyModule
内部的路由配置中有空路径规则:
const routes: Routes = [
{ path: '', component: LazyHomeComponent } // 默认组件
];
访问 /lazy
时,会直接渲染 LazyHomeComponent
,而无需重定向。
动态参数路由:
const routes: Routes = [
{ path: 'product/:id', component: ProductDetailComponent }
];
现象:
访问 /product/123
会直接渲染 ProductDetailComponent
,无需重定向。
场景 | 原因 |
---|---|
空路径 (path: '' ) |
直接匹配空路径对应的组件。 |
子路由的空路径 | 父组件渲染后,子路由的空路径组件会自动显示。 |
动态参数路由 (:id ) |
路径匹配后直接渲染组件。 |
通配符路由 (** ) |
兜底路由直接显示组件。 |
懒加载模块的默认路由 | 模块内部的路由配置可能已经定义了空路径组件。 |