You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

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

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.06.21 15:24:50