Angular使用createCustomElement时手动修改地址栏路由失效问题
问题场景
基于Angular 10.2.4、Node.js 14.24.1开发的微应用,将AppComponent通过createCustomElement转为自定义元素<my-app>部署。应用包含root-test特性模块,该模块下有feature-one和feature-two子组件,同时配置了模块独立路由和根路由规则。
本地运行npm start时,手动修改浏览器地址栏为http://localhost:4200/one无法路由到FeatureOneComponent,但通过按钮点击调用Router.navigate()的编程式路由可以正常跳转,尝试使用HashLocationStrategy也未解决问题。
相关代码文件
root-test-routing.module.ts
import { NgModule } from '@angular/core'; import { Routes, RouterModule } from '@angular/router'; import { FeatureOneComponent } from './feature-one/feature-one.component'; import { FeatureTwoComponent } from './feature-two/feature-two.component'; import { RootTestComponent } from './root-test.component'; const routes: Routes = [ { path:'',component:RootTestComponent }, { path:'one',component:FeatureOneComponent }, { path:'two',component:FeatureTwoComponent } ]; @NgModule({ imports: [RouterModule.forChild(routes)], exports: [RouterModule] }) export class RootTestRoutingModule { }
root-test.module.ts
import { NgModule } from '@angular/core'; import { CommonModule } from '@angular/common'; import { RootTestRoutingModule } from './root-test-routing.module'; import { RootTestComponent } from './root-test.component'; import { FeatureOneComponent } from './feature-one/feature-one.component'; import { FeatureTwoComponent } from './feature-two/feature-two.component'; @NgModule({ declarations: [ RootTestComponent, FeatureOneComponent, FeatureTwoComponent ], imports: [ RootTestRoutingModule ], providers: [], exports:[ RootTestComponent, FeatureOneComponent, FeatureTwoComponent] }) export class RootTestModule { }
app.module.ts
import { BrowserModule } from '@angular/platform-browser'; import { Injector, NgModule } from '@angular/core'; import { createCustomElement } from '@angular/elements'; import { AppComponent } from './app.component'; import { RootTestModule } from './root-test/root-test.module'; import { AppRoutingModule } from './app-routing.module'; @NgModule({ declarations: [ AppComponent ], imports: [ BrowserModule, RootTestModule, AppRoutingModule ], providers: [], // bootstrap: [AppComponent] }) export class AppModule { constructor(private injector: Injector) { } ngDoBootstrap() { const myMicroElement = createCustomElement(AppComponent, { injector: this.injector }); customElements.get('my-app') || customElements.define('my-app', myMicroElement); } }
app-routing.module.ts
import { NgModule } from '@angular/core'; import { Routes, RouterModule } from '@angular/router'; import { FeatureOneComponent } from './root-test/feature-one/feature-one.component'; import { FeatureTwoComponent } from './root-test/feature-two/feature-two.component'; import { RootTestComponent } from './root-test/root-test.component'; const routes: Routes = [ // { // path:'root', // loadChildren: () => import('./root-test/root-test.module').then(m => m.RootTestModule) // }, { path:'root',component:RootTestComponent }, { path:'one',component:FeatureOneComponent }, { path:'two',component:FeatureTwoComponent } ]; @NgModule({ imports: [RouterModule.forRoot(routes)], exports: [RouterModule] }) export class AppRoutingModule { }
app.component.html
<h4>hello</h4> <button (click)="routeToView()">Route</button> <router-outlet></router-outlet>
app.component.ts
import { Component } from '@angular/core'; import { Router } from '@angular/router'; @Component({ selector: 'app-root', templateUrl: './app.component.html', styleUrls: ['./app.component.scss'] }) export class AppComponent { title = 'microapp-test'; constructor(private _router: Router) {} public routeToView(): void { this._router.navigate(['one']) } }
index.html
<!doctype html> <html lang="en"> <head> <meta charset="utf-8"> <title>MicroappTest</title> <base href="/"> <meta name="viewport" content="width=device-width, initial-scale=1"> <link rel="icon" type="image/x-icon" href="favicon.ico"> </head> <body> <!-- <app-root></app-root> --> <my-app></my-app> </body> </html>
问题原因
当将Angular应用转为自定义元素(Web Component)时,Angular默认不会自动触发初始路由导航——普通Angular应用会通过bootstrap组件触发初始导航,但自定义元素的初始化逻辑不同,导致直接访问地址栏路由时,Angular还未完成路由初始化,无法捕获地址栏的路由变化。
另外,浏览器地址栏的全局路由变化事件,默认不会被Angular Elements内部的路由系统监听,手动刷新或直接输入路由时,无法同步到Angular路由状态。
解决方案
方案1:手动触发初始路由导航
修改app.component.ts,实现OnInit接口,在组件初始化时手动调用initialNavigation(),确保Angular路由处理地址栏的初始路径:
import { Component, OnInit } from '@angular/core'; import { Router } from '@angular/router'; @Component({ selector: 'app-root', templateUrl: './app.component.html', styleUrls: ['./app.component.scss'] }) export class AppComponent implements OnInit { title = 'microapp-test'; constructor(private _router: Router) {} ngOnInit(): void { // 手动触发初始导航,处理地址栏的初始路由 this._router.initialNavigation(); } public routeToView(): void { this._router.navigate(['one']) } }
方案2:监听全局路由变化事件
添加HostListener监听浏览器的popstate事件,当地址栏路径变化时,手动同步到Angular路由:
import { Component, OnInit, HostListener } from '@angular/core'; import { Router } from '@angular/router'; @Component({ selector: 'app-root', templateUrl: './app.component.html', styleUrls: ['./app.component.scss'] }) export class AppComponent implements OnInit { title = 'microapp-test'; constructor(private _router: Router) {} ngOnInit(): void { this._router.initialNavigation(); } // 监听浏览器地址栏变化 @HostListener('window:popstate') onPopState(): void { this._router.navigateByUrl(window.location.pathname + window.location.hash); } public routeToView(): void { this._router.navigate(['one']) } }
方案3:配置HashLocationStrategy(可选)
如果需要使用哈希路由,修改app-routing.module.ts,在forRoot中添加useHash: true配置:
@NgModule({ imports: [RouterModule.forRoot(routes, { useHash: true })], exports: [RouterModule] }) export class AppRoutingModule { }
此时访问路径会变为http://localhost:4200/#/one,结合方案1的初始导航代码,即可正常响应地址栏的手动输入。
验证
修改代码后重启npm start,直接在地址栏输入http://localhost:4200/one(或哈希模式下的http://localhost:4200/#/one),即可正常路由到FeatureOneComponent。
内容的提问来源于stack exchange,提问作者Abhinavan

