postgresql PEAN验证应用程序:为什么Angular拦截器总是使用BehaviorSubject返回null(即初始值),而不是更新后的值?

vecaoik1  于 2023-08-04  发布在  PostgreSQL
关注(0)|答案(2)|浏览(101)

我正在使用PEAN堆栈(即PostgreSQL - ExpressJS - Angular - NodeJS)构建一个身份验证应用程序。
我检查用户登录状态,如下所示:
1.在后端,检查会话cookie以查看req.session对象中是否存在user属性。

server.js

/* ... */

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' });
  }
});

/* ... */

字符串
1.向api/get-signin-status端点发送HTTP POST请求,其中包含可选数据,并在请求中包含Cookie。

auth.service.ts

/* ... */

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

/* ... */


1.拦截任何HTTP请求并提供可观察的interceptorResponse$用于订阅拦截请求的响应。

interceptor.service.ts

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) {}
}


1.在前端,订阅来自InterceptorServiceinterceptorResponse observable,并将响应记录到控制台。

header.component.ts

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 answer,我应该使用BehaviorSubject。问题是,在控制台中,我总是得到以下内容:
x1c 0d1x的数据
但是如果我控制台记录nexterror如下:

interceptor.service.ts

/* ... */

const signInStatusObserver = {
  next: (x: any) => {
    console.log(x);
    this.interceptorResponse$.next({ success: true, response: x });
  },

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

/* ... */


我在控制台中看到了预期的{message: 'User logged in'},如下面的屏幕截图所示。这意味着后端正确地将登录状态传递给前端。


提问

为什么Angular拦截器总是使用BehaviorSubject返回null(即初始值),而不是更新后的值?
编辑1
app.module.ts

import { NgModule } from '@angular/core';
import { BrowserModule } from '@angular/platform-browser';
import { BrowserAnimationsModule } from '@angular/platform-browser/animations';

import { HeaderComponent } from './header/header.component';
import { AppComponent } from './app.component';
import { FooterComponent } from './footer/footer.component';

import { AppRoutingModule } from './app-routing.module';
import { RoutingComponents } from './app-routing.module';

import { SharedModule } from './shared/shared.module';

import { HttpClientModule } from '@angular/common/http';

import { MatMenuModule } from '@angular/material/menu';
import { MatSidenavModule } from '@angular/material/sidenav';

import { CodeInputModule } from 'angular-code-input';

import { IfSignedOut } from './auth/guards/if-signed-out.guard';
import { IfSignedIn } from './auth/guards/if-signed-in.guard';

import { InterceptorService } from './auth/services/interceptor.service';
import { HTTP_INTERCEPTORS } from '@angular/common/http';

@NgModule({
  declarations: [HeaderComponent, AppComponent, FooterComponent, RoutingComponents],
  imports: [BrowserModule, BrowserAnimationsModule, AppRoutingModule, SharedModule, HttpClientModule, MatMenuModule, MatSidenavModule, CodeInputModule],
  providers: [IfSignedOut, IfSignedIn, { provide: HTTP_INTERCEPTORS, useClass: InterceptorService, multi: true }],
  bootstrap: [AppComponent],
})
export class AppModule {}

编辑2

在@VonC的帮助下,我设法让整个事情按预期进行。我是这么做的
1.我删除了我在server.js中的初始代码,因为拦截器现在将依赖于api/get-user端点,而不是像以前那样依赖于api/get-signin-status。因此,我不再需要app.post('/api/get-signin-status', () => {})。我现在使用api/get-user端点的原因是因为两者都做了同样的事情(即检查会话cookie以查看req.session对象中是否存在user属性),这意味着只有一个端点足以用于我的auth应用程序。不需要两次检查会话cookie。

server.js

/* ... */

/* Removed */
/*
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' });
  }
});
*/

/* ... */


1.我删除了我在auth.service.ts中的初始代码,并按照@VonC的建议添加了代码。

auth.service.ts

/* ... */

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

/* Added */
private signInStatus$: BehaviorSubject<any> = new BehaviorSubject<any>(null);

getSignInStatusObserver(): Observable<any> {
  return this.signInStatus$.asObservable();
}

setSignInStatus(status: any): void {
  this.signInStatus$.next(status);
}

/* ... */


1.我删除了我在interceptor.service.ts中的初始代码,并按照@VonC的建议添加了代码。* 注意:我将端点从api/get-signin-status更改为api/get-user。*

interceptor.service.ts

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

@Injectable({
  providedIn: 'root',
})
export class InterceptorService implements HttpInterceptor {
  intercept(httpRequest: HttpRequest<any>, next: HttpHandler): Observable<HttpEvent<any>> {
    return next.handle(httpRequest).pipe(
      tap((event: HttpEvent<any>) => {
        if (event instanceof HttpResponse && httpRequest.url.endsWith('api/get-user')) {
          this.authService.setSignInStatus({ success: true, response: event.body });
        }
      }),
      catchError((err: any) => {
        if (httpRequest.url.endsWith('api/get-user')) {
          this.authService.setSignInStatus({ success: false, response: err });
        }
        return throwError(err);
      })
    );
  }

  constructor(private authService: AuthService) {}
}


1.我删除了我在header.component.ts中的初始代码,并按照@VonC的建议添加了代码。

header.component.ts

import { Component, OnInit } from '@angular/core';
import { AuthService } from 'src/app/auth/services/auth.service';
import { Router } from '@angular/router';
import { MatSnackBar } from '@angular/material/snack-bar';

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

  constructor(private authService: AuthService, public publicAuthService: AuthService, private signOutRouter: Router, private snackBar: MatSnackBar) {
    this.authService.getSignInStatusObserver().subscribe((response: any) => {
      this.signInStatus = response;
      if (response) {
        console.log('Sign in status success:', response.response);
      } else {
        console.log('Sign in status is null');
      }
    });
  }

  ngOnInit(): void {}
}


现在我终于可以根据来自后端的登录状态显示header.component.html中的元素,如下所示:

header.component.html

<div *ngIf="signInStatus">Show this element if the user is signed in</div>
<div *ngIf="!signInStatus">Show this element if the user is signed out</div>

2uluyalo

2uluyalo1#

interceptor response is null“可能是预期的:您在拦截器中使用RxJSBehaviorSubject的方式可能会导致此问题。
拦截器的目的是拦截HTTP请求,并对这些请求或响应执行某些操作。
(See例如“Intro to Angular Http Interceptors”(来自**Cory Rylan**)
但是在您的拦截器中,您是通过调用this.authService.getSignInStatus().subscribe(signInStatusObserver);而不是拦截现有的HTTP请求来创建一个 * 新的 * HTTP请求。这意味着,当您的组件订阅getInterceptorResponse()时,对此请求的响应可能不会立即可用。
......因此可能是空拦截器响应。
例如,您可以使用拦截器来检查使用者的验证状态:

// interceptor.service.ts

import { tap, catchError } from 'rxjs/operators';

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

  intercept(httpRequest: HttpRequest<any>, next: HttpHandler): Observable<HttpEvent<any>> {
    return next.handle(httpRequest).pipe(
      tap((event: HttpEvent<any>) => {
        if (event instanceof HttpResponse && httpRequest.url.endsWith('/api/get-signin-status')) {
          this.authService.setSignInStatus({ success: true, response: event.body });
        }
      }),
      catchError((err: any) => {
        if (httpRequest.url.endsWith('/api/get-signin-status')) {
          this.authService.setSignInStatus({ success: false, response: err });
        }
        return throwError(err);
      })
    );
  }
}

字符串
在此更新版本中,我们订阅了nexthandle方法,该方法返回HTTP响应的Observable。它使用tap操作符处理成功的响应,并使用catchError操作符处理错误。
我假设setSignInStatus是您的AuthService中的一个方法,用于更新存储在BehaviorSubject中的登录状态。
我还在检查请求URL是否以“/api/get-signin-status”结尾,以确保我们仅为该特定请求设置登录状态。
这意味着您可以使用以下项实现此用户的身份验证状态:

// auth.service.ts

private signInStatus$: BehaviorSubject<any> = new BehaviorSubject<any>(null);

getSignInStatusObserver(): Observable<any> {
  return this.signInStatus$.asObservable();
}

setSignInStatus(status: any): void {
  this.signInStatus$.next(status);
}


您需要更新header.component.ts以使用authService而不是interceptorService

// header.component.ts

constructor(private authService: AuthService) {
  this.authService.getSignInStatusObserver().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');
    }
  });
}


最后,在您的组件中,您将订阅getSignInStatusObserver而不是getInterceptorResponse
这确保了每次从“/api/get-signin-status”收到HTTP响应时都更新登录状态,并且组件可以订阅状态更新。
组件中的订阅将如下所示:

// header.component.ts

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 {
  signInStatus: any;

  constructor(private authService: AuthService) {
    this.authService.getSignInStatusObserver().subscribe((response: any) => {
      // That block of code will run every time the signInStatus updates
      console.log(response);

      this.signInStatus = response;
      if (response) {
        console.log('Sign in status success:', response.response);
      } else {
        console.log('Sign in status is null');
      }
    });
  }

  ngOnInit(): void {}
}


HeaderComponent的构造函数中,我们注入的是AuthService,而不是InterceptorService。我们订阅的是getSignInStatusObserver(),它是AuthService中的一个方法,用于返回Observable的登录状态。当登录状态更新时,将使用新状态作为参数调用订阅的函数。
我们将登录状态存储在signInStatus属性中,并将其记录到控制台。如果响应存在,我们还将记录一条成功消息,否则将记录一条指示登录状态为null的消息。它反映了原始代码的行为。

stszievb

stszievb2#

我猜问题是Angular创建了你的interceptorService的多个示例。一个实际上用作拦截器,另一个被注入到您的组件/服务中。
我隐约记得有同样的问题。当时我是这样解决的:

class Interceptor{
    constructor(private final service: SharedDataService){}
}

class Component{
    constructor(private final service: SharedDataService){}
}

字符串
组件和拦截器都使用另一个服务来共享数据。

编辑

快速搜索实际上证实了这一点。参见Interceptor create two instances of singleton service

相关问题