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

Angular拦截器用BehaviorSubject始终返回初始null值的原因

基于PEAN栈的认证应用登录状态校验问题与修复

原登录状态校验流程

我采用PEAN栈(PostgreSQL - ExpressJS - Angular - NodeJS)构建认证应用,原登录状态校验流程如下:

1. 后端会话校验接口

后端通过检查会话Cookie中的req.session.user属性判断登录状态,接口实现:

/* ... */

app.post('/api/get-signin-status', async (req, res) => {
  try {
    if (req.session.user) {
      return res.status(200).json({ message: 'User logged in' });
    } else {
      return res.status(400).json({ message: 'User logged out' });
    }
  } catch {
    return res.status(500).json({ message: 'Internal server error' });
  }
});

/* ... */

2. 前端状态请求方法

前端通过携带Cookie的POST请求调用上述接口:

/* ... */

getSignInStatus(data?: any) {
  return this.http.post(this.authUrl + 'api/get-signin-status', data, {
    withCredentials: true,
  });
}

/* ... */

3. HTTP拦截器实现

通过拦截所有HTTP请求,利用BehaviorSubject提供登录状态的可观察对象:

import { Injectable } from '@angular/core';
import { HttpInterceptor, HttpEvent, HttpRequest, HttpHandler } from '@angular/common/http';
import { Observable, BehaviorSubject } from 'rxjs';
import { AuthService } from 'src/app/auth/services/auth.service';

@Injectable({
  providedIn: 'root',
})
export class InterceptorService implements HttpInterceptor {
  private interceptorResponse$: BehaviorSubject<any> = new BehaviorSubject<any>(null);

  intercept(httpRequest: HttpRequest<any>, next: HttpHandler): Observable<HttpEvent<any>> {
    const signInStatusObserver = {
      next: (x: any) => {
        this.interceptorResponse$.next({ success: true, response: x });
      },

      error: (err: any) => {
        this.interceptorResponse$.next({ success: false, response: err });
      },
    };

    this.authService.getSignInStatus().subscribe(signInStatusObserver);

    return next.handle(httpRequest);
  }

  getInterceptorResponse(): Observable<any> {
    return this.interceptorResponse$.asObservable();
  }

  constructor(private authService: AuthService) {}
}

4. 前端订阅状态并打印

在Header组件中订阅拦截器的可观察对象,打印响应结果:

import { Component, OnInit } from '@angular/core';
import { InterceptorService } from '../auth/services/interceptor.service';

@Component({
  selector: 'app-header',
  templateUrl: './header.component.html',
  styleUrls: ['./header.component.scss'],
})
export class HeaderComponent implements OnInit {
  interceptorResponse: any;

  constructor(
    private interceptorService: InterceptorService
  ) {
    this.interceptorService.getInterceptorResponse().subscribe((response: any) => {
      console.log(response);

      this.interceptorResponse = response;
      if (response) {
        console.log('Interceptor response success:', response.response);
      } else {
        console.log('Interceptor response is null');
      }
    });
  }

  ngOnInit(): void {}
}

遇到的问题

按照StackOverflow建议使用BehaviorSubject管理状态,但控制台始终输出初始值null,即便后端已正确返回{message: 'User logged in'}。

修复步骤

1. 替换后端接口

移除原/api/get-signin-status接口,改用/api/get-user接口,该接口直接返回当前登录用户信息(未登录则返回401状态):

/* ... */

app.get('/api/get-user', async (req, res) => {
  try {
    if (req.session.user) {
      // 返回用户完整信息而非仅状态提示
      return res.status(200).json(req.session.user);
    } else {
      return res.status(401).json({ message: 'Unauthorized' });
    }
  } catch {
    return res.status(500).json({ message: 'Internal server error' });
  }
});

/* ... */

2. 修改AuthService,内置状态管理

在auth.service.ts中新增BehaviorSubject直接管理登录状态,同时封装getUser方法:

import { Injectable } from '@angular/core';
import { HttpClient } from '@angular/common/http';
import { BehaviorSubject, Observable } from 'rxjs';
import { tap } from 'rxjs/operators';

@Injectable({
  providedIn: 'root',
})
export class AuthService {
  private authUrl = '/api/';
  // 用BehaviorSubject存储用户状态,初始值为null
  private currentUser$: BehaviorSubject<any | null> = new BehaviorSubject<any | null>(null);

  constructor(private http: HttpClient) {
    // 服务初始化时自动获取用户状态
    this.getUser().subscribe();
  }

  // 获取当前用户状态的可观察对象
  getCurrentUser(): Observable<any | null> {
    return this.currentUser$.asObservable();
  }

  // 调用后端接口更新用户状态
  getUser(): Observable<any> {
    return this.http.get(this.authUrl + 'get-user', { withCredentials: true }).pipe(
      tap((user) => {
        this.currentUser$.next(user);
      }),
      // 捕获未登录错误,将状态设为null
      tap({
        error: () => {
          this.currentUser$.next(null);
        }
      })
    );
  }
}

3. 调整HTTP拦截器逻辑

修改InterceptorService,不再在拦截所有请求时重复调用状态接口,改为仅拦截/api/get-user请求并同步状态(也可直接移除拦截器中状态相关逻辑,由AuthService统一管理):

import { Injectable } from '@angular/core';
import { HttpInterceptor, HttpEvent, HttpRequest, HttpHandler } from '@angular/common/http';
import { Observable } from 'rxjs';
import { tap } from 'rxjs/operators';
import { AuthService } from 'src/app/auth/services/auth.service';

@Injectable({
  providedIn: 'root',
})
export class InterceptorService implements HttpInterceptor {
  constructor(private authService: AuthService) {}

  intercept(httpRequest: HttpRequest<any>, next: HttpHandler): Observable<HttpEvent<any>> {
    // 仅在请求get-user接口时同步状态(可选,AuthService已自动处理)
    if (httpRequest.url.includes('get-user')) {
      return next.handle(httpRequest).pipe(
        tap(() => {
          this.authService.getUser().subscribe();
        })
      );
    }
    return next.handle(httpRequest);
  }
}

4. 组件直接订阅AuthService状态

修改header.component.ts,直接订阅AuthService的用户状态并更新UI:

import { Component, OnInit } from '@angular/core';
import { AuthService } from '../auth/services/auth.service';

@Component({
  selector: 'app-header',
  templateUrl: './header.component.html',
  styleUrls: ['./header.component.scss'],
})
export class HeaderComponent implements OnInit {
  currentUser: any | null = null;

  constructor(private authService: AuthService) {}

  ngOnInit(): void {
    this.authService.getCurrentUser().subscribe((user) => {
      this.currentUser = user;
      console.log('当前用户状态:', user);
      // 根据user是否为null,展示登录/登出按钮等对应UI
    });
  }
}

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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.07.16 10:42:47