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

Angular 17登录后Navbar按钮未切换问题求助

登录状态持久化问题(Angular 17 + PrimeNG 17)

提交正确的用户凭证后,服务器验证通过返回JWT Token、refresh token及isLoggedIn=true。当前代码已将token存入localStorage,但导航栏的Login按钮未切换为Logout,登录状态无法持久显示。


现有代码

export class NavbarComponent {
  menuItems: MenuItem[] = [];
  isLoggedIn: Observable<boolean>;
  ref: DynamicDialogRef | undefined;

  constructor(
    public dialogService: DialogService,
    private authService: AuthserviceService
  ) {
    this.isLoggedIn = authService.isLoggedIn();
  }

  ngOnInit() {
    this.menuItems = [//Menu Items would go here]
  }

  OpenLoginDialog(){
    this.ref = this.dialogService.open(LoginComponent, {
      header: 'Login',
      width: '50vw',
      modal:true,
      breakpoints: {
          '960px': '75vw',
          '640px': '90vw'
      },
    })
  }
}
<div>
  <p-menubar [model]="menuItems">
    <div>
      <input type="text" pInputText placeholder="Search">
      <button *ngIf="!isLoggedIn; else loggedOut" type="button" pButton label="Logout" icon="pi pi-power-off" style="margin-left:.25em"></button>
      <ng-template #loggedOut>
        <button type="button" pButton (click)="OpenLoginDialog()" label="Login" icon="pi pi-power-off" style="margin-left:.25em"></button>
      </ng-template>
    </div>
  </p-menubar>
</div>

Auth.service.ts

export class AuthserviceService {
  isLoginSubject = new BehaviorSubject<boolean>(this.hasToken());

  constructor(private http: HttpClient) { }

  loginUser<LoginResponse>(loginCreds: any){
    return this.http.post(baseUrl + "login", loginCreds)
  }

  logoutUser(){
    // This is where I delete the local storage 
  }

  setStorage(data: any){
    localStorage.setItem("token", data.jwtToken)
    localStorage.setItem("refresh", data.refreshToken)
    // Seem redundant so commented bottom line
    // localStorage.setItem("isLoggedIn", data.isLoggedIn.toString())
  }

  isLoggedIn(): Observable<boolean>{
    return this.isLoginSubject.asObservable()
  }

  private hasToken(): boolean{
    return !!localStorage.getItem("token")
  }
}

Login.component.ts

export class LoginComponent {

  loginCredentials: FormGroup;

  constructor(public ref: DynamicDialogRef, private formBuilder: FormBuilder, private auth: AuthserviceService) {
    this.loginCredentials = this.formBuilder.group({
      alias: ['', Validators.required],
      password: ['', Validators.required]
    })
  }

  SubmitCredentials() {
    console.log("Submitting Creds")
    this.auth.loginUser<LoginResponse>(this.loginCredentials.value)
    .subscribe({
      next: (data: any) => {
      console.log('Data: ' + data.jwtToken)
      console.log('Data: ' + data.refreshToken)
      console.log('Data: ' + data.isLoggedIn)
      this.auth.setStorage(data)
    },
    error: (error: HttpErrorResponse) => {
      console.log('Error: ' + error.error)
    }});

    this.closeDialog(localStorage.getItem("isLoggedIn"))
  }

  closeDialog(data: any = null){
    this.ref.close(data)
  }
}

问题原因

  1. BehaviorSubject未同步状态:登录成功后仅存入token到localStorage,但未通知isLoginSubject更新状态,导致Navbar的订阅者无法感知登录状态变化。
  2. 模板未正确订阅Observable:isLoggedIn是Observable类型,模板直接使用!isLoggedIn无法获取实时值,必须通过async管道订阅。
  3. 对话框关闭时机错误:closeDialog在subscribe外部调用,可能登录请求尚未完成就关闭对话框,且未依赖AuthService的状态更新。
  4. ngIf逻辑与模板命名冲突:*ngIf="!isLoggedIn; else loggedOut"逻辑反转,loggedOut模板实际显示Login按钮,命名和逻辑不匹配易导致混淆。

解决方案

1. 更新AuthService,同步状态变化

修改setStorage方法,存入token后更新BehaviorSubject;完善logoutUser方法,清除存储并同步状态:

export class AuthserviceService {
  isLoginSubject = new BehaviorSubject<boolean>(this.hasToken());

  constructor(private http: HttpClient) {
    // 可选:监听localStorage变化,处理多标签页登录状态同步
    window.addEventListener('storage', (event) => {
      if (event.key === 'token') {
        this.isLoginSubject.next(this.hasToken());
      }
    });
  }

  loginUser<LoginResponse>(loginCreds: any){
    return this.http.post(baseUrl + "login", loginCreds)
  }

  logoutUser(){
    localStorage.removeItem("token");
    localStorage.removeItem("refresh");
    this.isLoginSubject.next(false);
  }

  setStorage(data: any){
    localStorage.setItem("token", data.jwtToken)
    localStorage.setItem("refresh", data.refreshToken)
    // 更新登录状态通知所有订阅者
    this.isLoginSubject.next(true);
  }

  isLoggedIn(): Observable<boolean>{
    return this.isLoginSubject.asObservable()
  }

  private hasToken(): boolean{
    return !!localStorage.getItem("token")
  }
}

2. 修正Navbar模板,正确使用async管道

调整ngIf逻辑和模板命名,确保通过async管道订阅Observable:

<div>
  <p-menubar [model]="menuItems">
    <div>
      <input type="text" pInputText placeholder="Search">
      <!-- 登录状态显示Logout按钮,否则显示Login按钮 -->
      <button *ngIf="isLoggedIn | async; else loginTemplate" type="button" pButton label="Logout" icon="pi pi-power-off" style="margin-left:.25em" (click)="authService.logoutUser()"></button>
      <ng-template #loginTemplate>
        <button type="button" pButton (click)="OpenLoginDialog()" label="Login" icon="pi pi-power-off" style="margin-left:.25em"></button>
      </ng-template>
    </div>
  </p-menubar>
</div>

3. 调整LoginComponent对话框关闭时机

将closeDialog移到subscribe的next回调中,确保登录成功后再关闭对话框:

export class LoginComponent {

  loginCredentials: FormGroup;

  constructor(public ref: DynamicDialogRef, private formBuilder: FormBuilder, private auth: AuthserviceService) {
    this.loginCredentials = this.formBuilder.group({
      alias: ['', Validators.required],
      password: ['', Validators.required]
    })
  }

  SubmitCredentials() {
    console.log("Submitting Creds")
    this.auth.loginUser<LoginResponse>(this.loginCredentials.value)
    .subscribe({
      next: (data: any) => {
        console.log('Data: ' + data.jwtToken)
        console.log('Data: ' + data.refreshToken)
        console.log('Data: ' + data.isLoggedIn)
        this.auth.setStorage(data);
        // 登录成功后关闭对话框
        this.closeDialog();
      },
      error: (error: HttpErrorResponse) => {
        console.log('Error: ' + error.error)
      }
    });
  }

  closeDialog(data: any = null){
    this.ref.close(data)
  }
}

额外说明

  • 页面刷新时,isLoginSubject初始值由hasToken()从localStorage读取,确保刷新后状态保持一致。
  • 多标签页状态同步通过监听storage事件实现,可选但能提升用户体验。

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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.06.19 11:29:55