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

Angular Material Sidenav路由切换后CSS异常问题求助

问题:Mat-Sidenav路由切换后首个菜单项异常显示Hover/Focus样式

我使用mat-sidenav实现路由跳转,切换新路由后遇到CSS异常问题:通过sidenav点击进入某路由页面,再次打开sidenav准备切换路由时,第一个路由项会出现hover/focus的背景效果,视觉体验不佳。

html template file

<mat-sidenav-container class="sidenav-container">
  <mat-sidenav #drawer class="sidenav" fixedInViewport [attr.role]="(isHandset$ | async) ? 'dialog' : 'navigation'"
    [mode]="(isHandset$ | async) ? 'over' : 'side'" [opened]="(isHandset$ | async) === false">
    <div class="d-flex flex-column justify-content-center align-items-center py-2">
      <img class="avatar my-2" src="./assets/Images/DefaultPicture.jpg" />
      <div class="d-flex justify-content-center flex-column text-center">
        <p class="name md-info sm-header mb-0">{{ProfileDatails?.title[0]?.id != 'other' ? ProfileDatails?.title[0]?.title_name:
          ProfileDatails?.other_title }}</p>
          <h5 class="">{{ProfileDatails?.first_name}} {{ ProfileDatails?.last_name }}</h5>
      </div>
    </div>

    <mat-divider></mat-divider>

    <mat-nav-list>
      <a routerLinkActive="active_list_item" mat-list-item routerLink="appointments">
        <mat-icon class="me-2 post-rel-top-5 fw-bold">list_alt</mat-icon>
        <span>Poojas</span>
      </a>

      <a routerLinkActive="active_list_item" mat-list-item routerLink="host">
        <mat-icon class="me-2 post-rel-top-5">group</mat-icon>
        <span>Yajmans</span>
      </a>

      <a routerLinkActive="active_list_item" mat-list-item routerLink="pooja">
        <mat-icon class="me-2 post-rel-top-5">edit_square</mat-icon>
        <span>Pooja's & Material</span>
      </a>

      <a routerLinkActive="active_list_item" mat-list-item routerLink="settings">
        <mat-icon class="me-2 post-rel-top-5">settings</mat-icon>
        <span>Settings</span>
      </a>
    </mat-nav-list>

    <mat-divider></mat-divider>
    <mat-nav-list>
      <a mat-list-item (click)="openDialog()">
        <mat-icon class="me-2 post-rel-top-5">logout</mat-icon>
        <span>Logout</span>
      </a>
    </mat-nav-list>
  </mat-sidenav>
  <mat-sidenav-content class="overflow-x-hidden">
    <mat-toolbar color="primary" class="z-3">
      <button type="button" aria-label="Toggle sidenav" mat-icon-button (click)="drawer.toggle()"
        *ngIf="isHandset$ | async">
        <mat-icon aria-label="Side nav toggle icon">menu</mat-icon>
      </button>
    </mat-toolbar>
    <mat-progress-bar class="position-absolute" color="accent" mode="indeterminate" value="40"
      *ngIf="this.ds.progressbar"></mat-progress-bar>
    <router-outlet></router-outlet>
  </mat-sidenav-content>
</mat-sidenav-container>

ts File

import { AfterViewChecked, AfterViewInit, ChangeDetectorRef, Component, OnChanges, OnInit, SimpleChanges, ViewChild, inject } from '@angular/core';
import { BreakpointObserver, Breakpoints } from '@angular/cdk/layout';
import { Observable, firstValueFrom } from 'rxjs';
import { filter, map, shareReplay, withLatestFrom } from 'rxjs/operators';
import { MatSidenav } from '@angular/material/sidenav';
import { NavigationEnd, Router } from '@angular/router';
import { LogoutComponent } from './logout/logout.component';
import { MatDialog } from '@angular/material/dialog';
import { DashboardService } from './dashboard.service';
import { Title } from '@angular/platform-browser';
import { CommonService } from '../services/common.service';
import { ToastrService } from 'ngx-toastr';

@Component({
  selector: 'app-dashboard',
  templateUrl: './dashboard.component.html',
  styleUrls: ['./dashboard.component.css']
})
export class DashboardComponent implements OnInit, AfterViewInit, OnChanges, AfterViewChecked {
  @ViewChild(MatSidenav) sidenav!: MatSidenav;
  private breakpointObserver = inject(BreakpointObserver);

  constructor(private titleService: Title, private router: Router, public dialog: MatDialog, public ds: DashboardService, private cdr: ChangeDetectorRef, private comserv: CommonService, private toaster: ToastrService) {
    router.events.pipe(withLatestFrom(this.isHandset$),
      filter(([a, b]) => b && a instanceof NavigationEnd)
    ).subscribe(_ => {  
      //  this.sidenav?.close()
    }
    );
  }

  theme!: string;
  ProfileDatails: any;
  ngOnInit(): void {
    this.getProfile();
    this.titleService.setTitle("Panditji | Dashboard");
    let theme = localStorage.getItem('theme');
    this.theme = theme || 'theme-light';
    this.ds.renderBodywithClass(this.theme);
  }

  async getProfile() {
    try {
      let res: any = await firstValueFrom(this.ds.getProfile());
      if (res.status) {
        this.ProfileDatails = res.data;
      }
    } catch (e: any) {
      if (e.error.code == 401 || e.error.code == 403) {
        this.ds.sessionTimeout();
      } else {
        this.toaster.error('', e.error?.message ? e.error.message : 'Something went wrong');
      }
    }
  }

  ngAfterViewInit(): void {
    this.cdr.detectChanges();
  }

  ngOnChanges(changes: SimpleChanges): void { }

  ngAfterViewChecked() {
    this.cdr.detectChanges();
  }

  isHandset$: Observable<boolean> = this.breakpointObserver.observe(Breakpoints.Handset)
    .pipe(
      map(result => result.matches),
      shareReplay()
    );

  openDialog(): void {
    const dialogRef = this.dialog.open(LogoutComponent, {
      width: '340px',
    });
  }
}

CSS file

.sidenav-container {
  height: 100%;
}

.sidenav {
  width: 250px;
}

.sidenav .mat-toolbar {
  background: inherit;
}

.mat-toolbar.mat-primary {
  position: sticky;
  top: 0;
  z-index: 1;
}

.post-rel-top-5 {
  position: relative;
  top: 5px;
}

.active_list_item {
  border-left: 5px solid #ff8f00;
  background-color: #b8b8b8;
  word-wrap: normal;
}

.active_list_item>a {
  word-wrap: normal;
  background-color: #b8b8b8;
  border-left: 5px solid #ff8f00;
}

.z-index {
  z-index: 1000;
}

.avatar {
  vertical-align: middle;
  width: 100px;
  height: 100px;
  border-radius: 50%;
}

解决方案

这个问题的核心原因是路由切换后,浏览器焦点自动回落到sidenav内的首个可交互元素(即第一个路由链接),触发了Material组件默认的focus/hover样式。以下是几种可行的修复方案:

方案1:手动重置焦点到sidenav容器

修改TS文件中的路由事件监听逻辑,在路由切换完成后将焦点设置到sidenav本身,避免焦点落在菜单项上:

constructor(/* 保留原有注入 */) {
  router.events.pipe(
    withLatestFrom(this.isHandset$),
    filter(([a, b]) => b && a instanceof NavigationEnd)
  ).subscribe(async () => {  
    // 移动端路由切换后重置侧边栏状态并转移焦点
    if(this.sidenav.opened) {
      await this.sidenav.close().toPromise();
      await this.sidenav.open().toPromise();
    }
    // 将焦点设置到sidenav容器,而非菜单项
    this.sidenav.nativeElement.focus();
  });
}

方案2:CSS强制覆盖未激活项的焦点/hover样式

通过CSS明确移除未激活菜单项的focus和hover背景效果,只保留激活项的样式:

/* 移除未激活菜单项的hover/focus背景 */
.mat-nav-list a.mat-list-item:not(.active_list_item):hover,
.mat-nav-list a.mat-list-item:not(.active_list_item):focus {
  background-color: transparent !important;
}

/* 确保激活项样式优先级 */
.mat-nav-list a.mat-list-item.active_list_item {
  border-left: 5px solid #ff8f00;
  background-color: #b8b8b8 !important;
}

方案3:精确匹配路由激活状态

给routerLinkActive添加精确匹配配置,避免路由前缀匹配导致激活状态异常,间接影响焦点样式:

<a routerLinkActive="active_list_item" 
   [routerLinkActiveOptions]="{ exact: true }" 
   mat-list-item routerLink="appointments">
  <mat-icon class="me-2 post-rel-top-5 fw-bold">list_alt</mat-icon>
  <span>Poojas</span>
</a>

将上述配置应用到所有带routerLink的菜单项上。

内容的提问来源于stack exchange,提问作者ErMapsh

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.07.04 06:13:10