Angular多模块新增子模块后路由跳转URL变UI不更新排查求助
Angular路由跳转URL变更但UI不更新问题排查
问题背景
维护多模块Angular项目,新增AdvertisingModule子模块后,路由与代码配置无报错。点击模块内按钮跳转至子页面时,控制台无错误提示,URL正常变化,但UI停留在原页面未更新。
相关代码
App路由模块代码
import { NgModule } from '@angular/core'; import { Routes, RouterModule, PreloadAllModules } from '@angular/router'; import { PagesComponent } from './pages/pages.component'; import { NotFoundComponent } from './pages/not-found/not-found.component'; import { AdminGuardService } from './Auth/admin-guard.service'; export const routes: Routes = [ { path: '', component: PagesComponent, children: [ { path: 'home', loadChildren: () => import('./pages/home/home.module').then(m => m.HomeModule) }, { path: 'account', loadChildren: () => import('./pages/account/account.module').then(m => m.AccountModule), data: { breadcrumb: 'Account Settings' } }, { path: 'compare', loadChildren: () => import('./pages/compare/compare.module').then(m => m.CompareModule), data: { breadcrumb: 'Compare' } }, { path: 'wishlist', loadChildren: () => import('./pages/wishlist/wishlist.module').then(m => m.WishlistModule), data: { breadcrumb: 'Wishlist' } }, { path: 'cart', loadChildren: () => import('./pages/cart/cart.module').then(m => m.CartModule), data: { breadcrumb: 'Shopping Cart' } }, { path: 'checkout', loadChildren: () => import('./pages/checkout/checkout.module').then(m => m.CheckoutModule), data: { breadcrumb: 'Checkout' }}, { path: 'contact', loadChildren: () => import('./pages/contact/contact.module').then(m => m.ContactModule), data: { breadcrumb: 'Contact' }}, { path: 'brands', loadChildren: () => import('./pages/brands/brands.module').then(m => m.BrandsModule), data: { breadcrumb: 'Brands' } }, { path: 'products', loadChildren: () => import('./pages/products/products.module').then(m => m.ProductsModule), data: { breadcrumb: 'All Products' }} ] }, { path: 'retail', loadChildren: () => import('./retail/retail.module').then(m => m.RetailModule), data: { breadcrumb: 'Foggy Frog Retail' }}, { path: 'landing', loadChildren: () => import('./landing/landing.module').then(m => m.LandingModule),canActivate : [AdminGuardService] }, { path: 'admin', loadChildren: () => import('./admin/admin.module').then(m => m.AdminModule) , canActivate : [AdminGuardService]}, { path: 'sign-in', loadChildren: () => import('./pages/sign-in/sign-in.module').then(m => m.SignInModule), data: { breadcrumb: 'Sign In ' }}, { path: 'advertiesments', loadChildren: () => import('./advertising/advertising.module').then(m => m.AdvertisingModule), data: { breadcrumb: 'Foggy Frog Advertising' }}, { path: '**', component: NotFoundComponent } ]; @NgModule({ imports: [ RouterModule.forRoot(routes, { preloadingStrategy: PreloadAllModules, // <- comment this line for activate lazy load relativeLinkResolution: 'legacy', initialNavigation: 'enabled' // for one load page, without reload // useHash: true }) ], exports: [ RouterModule ] }) export class AppRoutingModule { }
AdvertisingModule代码
import { NgModule } from '@angular/core'; import { CommonModule } from '@angular/common'; import { SelectListComponent } from './select-list/select-list.component'; import { PictureViewerComponent } from './picture-viewer/picture-viewer.component'; import { ReactiveFormsModule, FormsModule } from '@angular/forms'; import { MatCardModule } from '@angular/material/card'; import { RouterModule } from '@angular/router'; import { InfiniteScrollModule } from 'ngx-infinite-scroll'; import { InputFileConfig, InputFileModule } from 'ngx-input-file'; import { SharedModule } from '../shared/shared.module'; import { selectlistResolver } from './select-list/selectlistResolver'; import { pictureViewerResolver } from './picture-viewer/pictureViewerResolver'; import { advertisingComponent } from './advertising.component'; const config: InputFileConfig = { fileAccept: '*' }; export const routes = [ { path : 'internal', component : advertisingComponent, children : [ { path : 'list' , component : SelectListComponent , resolve : { cres : selectlistResolver }}, { path : 'preview/:listID' , component : PictureViewerComponent ,data: { breadcrumb: 'Manage User Account' },resolve : {cres : pictureViewerResolver}} ] } ] @NgModule({ declarations: [PictureViewerComponent,SelectListComponent,advertisingComponent], imports: [ ReactiveFormsModule, FormsModule, MatCardModule, RouterModule.forChild(routes), SharedModule, InfiniteScrollModule, CommonModule ] , providers : [selectlistResolver,pictureViewerResolver] }) export class AdvertisingModule { }
advertisingComponent代码
import { Component, Inject, OnInit } from '@angular/core'; import { MAT_DIALOG_DATA, MatDialogRef } from '@angular/material/dialog'; import { ActivatedRoute, Router } from '@angular/router'; import { map } from 'rxjs'; import { TokenWithEmail } from 'src/app/Global/Models'; import { ApiserviceService } from 'src/app/Global/apiservice.service'; import { advertisingInfo } from 'src/app/app.models'; @Component({ selector: 'app-select-list', templateUrl: './advertising.component.html', styleUrls: ['./advertising.component.scss'] }) export class advertisingComponent implements OnInit { constructor(private apiservice : ApiserviceService, private router : Router) { } ngOnInit(): void { console.log("Image list Open"); // this.router.navigate(['/advertiesments/internal/list']); } async OpenList() { this.router.navigate(['/preview/38c7f9ec-fd0b-4338-9959-e254c32678b9']); } }
advertisingComponent HTML代码
<h4 class="py-2"> Available Advertisement Lists </h4> <button mat-raised-button (click)="OpenList()">Action</button>
PictureViewerComponent代码
HTML
<div class="picture-viewer" > <img [src]="pictures[currentIndex]?.imagepath" alt="Picture" class="picture" /> <div class="controls"> <button mat-icon-button (click)="toggleFullScreen()"> <!-- <mat-icon>{{ isFullScreen ? 'fullscreen_exit' : 'fullscreen' }}</mat-icon> --> </button> <button mat-icon-button (click)="refreshPictures()"> <!-- <mat-icon>refresh</mat-icon> --> </button> </div> </div>
TypeScript
import { Component, OnInit } from '@angular/core'; import { IImage } from 'src/app/app.models'; import { ApiserviceService } from 'src/app/Global/apiservice.service'; @Component({ selector: 'app-picture-viewer', templateUrl: './picture-viewer.component.html', styleUrls: ['./picture-viewer.component.scss'] }) export class PictureViewerComponent implements OnInit { pictures: IImage[] = []; currentIndex = 0; intervalId: any; isFullScreen = false; constructor(private apiservice : ApiserviceService) { } ngOnInit(): void { console.log("CCCCCCCCCC *** I came Here"); this.loadPictures(); document.addEventListener('keydown', (event) => { if (event.key === 'Escape' && this.isFullScreen) { this.exitFullScreen(); } }); } loadPictures(): void { this.apiservice.GetAdvertisingSingleList('38c7f9ec-fd0b-4338-9959-e254c32678b9').subscribe(x=>{ this.pictures = x.imagelist; console.log(this.pictures); this.startSlideshow(); }); // this.pictureService.fetchPictures().subscribe(pictures => { // this.pictures = pictures; // this.startSlideshow(); // }, error => { // this.pictures = this.pictureService.getCachedPictures(); // this.startSlideshow(); // }); } startSlideshow(): void { this.intervalId = setInterval(() => { this.currentIndex = (this.currentIndex + 1) % this.pictures.length; }, 3000); // Change picture every 3 seconds } stopSlideshow(): void { clearInterval(this.intervalId); } refreshPictures(): void { this.stopSlideshow(); this.loadPictures(); } toggleFullScreen(): void { this.isFullScreen = !this.isFullScreen; if (this.isFullScreen) { document.documentElement.requestFullscreen(); } else { document.exitFullscreen(); } } exitFullScreen(): void { if (this.isFullScreen) { document.exitFullscreen(); this.isFullScreen = false; } } }
排查步骤与解决方案
1. 修复父组件缺失的<router-outlet>
advertisingComponent作为internal路由的父组件,其模板必须包含<router-outlet>标签,否则子路由对应的组件(如PictureViewerComponent)无法渲染。修改advertising.component.html:
<h4 class="py-2"> Available Advertisement Lists </h4> <button mat-raised-button (click)="OpenList()">Action</button> <!-- 添加路由出口,用于渲染子路由组件 --> <router-outlet></router-outlet>
2. 修正路由跳转路径
当前advertisingComponent的OpenList方法中跳转路径为/preview/xxx,这是错误的绝对路径,正确路径应包含完整的层级:/advertiesments/internal/preview/xxx。修改跳转代码:
async OpenList() { this.router.navigate(['/advertiesments/internal/preview/38c7f9ec-fd0b-4338-9959-e254c32678b9']); }
或者使用相对路径(更推荐,避免硬编码前缀):
async OpenList() { // 相对当前路由跳转,需注入ActivatedRoute this.router.navigate(['preview/38c7f9ec-fd0b-4338-9959-e254c32678b9'], { relativeTo: this.route }); } // 记得在构造函数注入ActivatedRoute constructor(private apiservice : ApiserviceService, private router : Router, private route: ActivatedRoute) { }
3. 检查Resolver是否阻塞路由
两个子路由都配置了resolve,若Resolver未正常返回数据(如请求报错、无限pending),会导致路由无法激活。可临时注释resolve配置,测试跳转是否正常:
export const routes = [ { path : 'internal', component : advertisingComponent, children : [ { path : 'list' , component : SelectListComponent /*, resolve : { cres : selectlistResolver }*/}, { path : 'preview/:listID' , component : PictureViewerComponent ,data: { breadcrumb: 'Manage User Account' } /*,resolve : {cres : pictureViewerResolver}*/} ] } ]
若注释后跳转正常,需排查Resolver内的逻辑,确保其能正确返回数据或抛出可捕获的错误。
4. 验证路由层级匹配
确认App路由与AdvertisingModule路由的层级是否正确:
- App路由中
advertiesments对应懒加载AdvertisingModule - AdvertisingModule中
internal是父路由,子路由list和preview/:listID需通过/advertiesments/internal/list、/advertiesments/internal/preview/xxx访问,跳转路径必须严格匹配此结构。
内容的提问来源于stack exchange,提问作者CrazyCoder
相关产品推荐
相关产品推荐

