在拦截器内部使用可观察对象
Use an observable inside of an interceptor
我想编写一个拦截器来为所有请求添加一个授权令牌。我的令牌来自一个库,angularx-social-login,它只提供一个 Observable 来获取令牌。所以我写了这个但是我的请求从未发送过,就好像这个值从未从可观察的输出中一样。
import { HttpEvent, HttpHandler, HttpInterceptor, HttpRequest} from "@angular/common/http";
import { SocialAuthService } from "angularx-social-login";
import {Observable, switchMap} from "rxjs";
import {Injectable} from "@angular/core";
@Injectable()
export class AuthInterceptorService implements HttpInterceptor {
constructor(private authService: SocialAuthService) {}
intercept(request: HttpRequest<any>, next: HttpHandler): Observable<HttpEvent<any>> {
return this.authService.authState.pipe(
switchMap((user) => {
const token = user.idToken
if (token) {
request = request.clone({
setHeaders: {Authorization: `Bearer ${token}`}
});
}
return next.handle(request)
})
);
}
}
我不是 100% 确定这一点,但我认为使用 switchMap
你正在切换到一个新的 observable,所以 return next.handle(request)
永远不会被下一个。似乎 authState 是一个可观察的,所以我们可以 pipe(take(1))
而无需取消订阅。
我会更简洁,将获取令牌和设置 Bearer 分开。
import { HttpEvent, HttpHandler, HttpInterceptor, HttpRequest } from '@angular/common/http';
import { SocialAuthService } from 'angularx-social-login';
import { Observable } from 'rxjs';
import { Injectable } from '@angular/core';
@Injectable()
export class AuthInterceptorService implements HttpInterceptor {
constructor(private authService: SocialAuthService) {}
intercept( request: HttpRequest<any>, next: HttpHandler): Observable<HttpEvent<any>> {
let token;
this.authService.authState.pipe(take(1)).subscribe(data => token = data);
if (token) {
// If we have a token, we set it to the header
request = request.clone({
setHeaders: { Authorization: `Bearer ${token}` },
});
}
return next.handle(request).pipe(
catchError((err) => {
if (err instanceof HttpErrorResponse) {
if (err.status === 401) {
// redirect user to the logout page
}
}
return throwError(err);
})
);
}
}
还建议重构接收令牌的方式。就像将它更新到某个存储并通过 authService 从存储接收它一样。每次订阅一个 observable 是一件痛苦的事情。
AuthService.ts
getAuthToken():string {
return localeStarage.getItem('token')
}
然后你可以这样做:
const token = this.authService.getAuthToken();
我想这里发生的事情是,当订阅 observable 时,authState 的值已经在登录过程中发出,因此请求挂起等待发出新值,这已经发生了在登录过程中。
为了解决这个问题,我建议你实现一个服务(providedInRoot)注入到登录组件中,并在登录过程中检索用户数据。
您可以在 Login 组件的 OnInit 中订阅 SocialAuthService 服务的 authState observable:
import { Component, OnInit, OnDestroy } from '@angular/core';
import { SocialAuthService } from "angularx-social-login";
import { Subject } from 'rxjs';
import { takeUntil } from 'rxjs/operators';
// ... the rest of import
@Component({
// ... Component decorator props (selector, templateUrl, styleUrls)
})
export class LoginComponent implements OnInit, OnDestroy {
destroy = new Subject<boolean>();
constructor(private authService: SocialAuthService, myUserService: MyUserService) { }
ngOnInit(): void {
this.authService.authState.pipe(takeUntil(this.destroy)).subscribe((user) => {
this.myUserService.user = user;
});
}
// ... the rest of the component
ngOnDestroy(): void {
this.destroy.next(true);
this.destroy.complete();
}
}
然后您可以在拦截器中使用 myUserService.user
中的值来检索令牌。
我已经将 takeUntil rxjs 运算符与 ngOnDestroy 的主题一起使用,但您也可以将订阅存储为 class 变量并执行取消订阅。
import { Component, OnInit, OnDestroy } from '@angular/core';
import { SocialAuthService } from "angularx-social-login";
import { Subscription } from 'rxjs';
// ... the rest of import
@Component({
// ... Component decorator props (selector, templateUrl, styleUrls)
})
export class LoginComponent implements OnInit, OnDestroy {
authStateSubscription: Subscription;
constructor(private authService: SocialAuthService, myUserService: MyUserService) { }
ngOnInit(): void {
this.authStateSubscription = this.authService.authState.subscribe((user) => {
this.myUserService.user = user;
});
}
// ... the rest of the component
ngOnDestroy(): void {
this.authStateSubscription.unsubscribe();
}
}
两种方式都应该有效。
我倾向于同意@cybering 的解释,要添加到他的解决方案中,您可以将 authState
定义为 BehaviorSubject。这样,无论某个组件何时订阅它,它都会获得一个发射值,因为 BehaviorSubject“保存”最后发射的值并在新订阅时发射它而不依赖于时间。
我想编写一个拦截器来为所有请求添加一个授权令牌。我的令牌来自一个库,angularx-social-login,它只提供一个 Observable 来获取令牌。所以我写了这个但是我的请求从未发送过,就好像这个值从未从可观察的输出中一样。
import { HttpEvent, HttpHandler, HttpInterceptor, HttpRequest} from "@angular/common/http";
import { SocialAuthService } from "angularx-social-login";
import {Observable, switchMap} from "rxjs";
import {Injectable} from "@angular/core";
@Injectable()
export class AuthInterceptorService implements HttpInterceptor {
constructor(private authService: SocialAuthService) {}
intercept(request: HttpRequest<any>, next: HttpHandler): Observable<HttpEvent<any>> {
return this.authService.authState.pipe(
switchMap((user) => {
const token = user.idToken
if (token) {
request = request.clone({
setHeaders: {Authorization: `Bearer ${token}`}
});
}
return next.handle(request)
})
);
}
}
我不是 100% 确定这一点,但我认为使用 switchMap
你正在切换到一个新的 observable,所以 return next.handle(request)
永远不会被下一个。似乎 authState 是一个可观察的,所以我们可以 pipe(take(1))
而无需取消订阅。
我会更简洁,将获取令牌和设置 Bearer 分开。
import { HttpEvent, HttpHandler, HttpInterceptor, HttpRequest } from '@angular/common/http';
import { SocialAuthService } from 'angularx-social-login';
import { Observable } from 'rxjs';
import { Injectable } from '@angular/core';
@Injectable()
export class AuthInterceptorService implements HttpInterceptor {
constructor(private authService: SocialAuthService) {}
intercept( request: HttpRequest<any>, next: HttpHandler): Observable<HttpEvent<any>> {
let token;
this.authService.authState.pipe(take(1)).subscribe(data => token = data);
if (token) {
// If we have a token, we set it to the header
request = request.clone({
setHeaders: { Authorization: `Bearer ${token}` },
});
}
return next.handle(request).pipe(
catchError((err) => {
if (err instanceof HttpErrorResponse) {
if (err.status === 401) {
// redirect user to the logout page
}
}
return throwError(err);
})
);
}
}
还建议重构接收令牌的方式。就像将它更新到某个存储并通过 authService 从存储接收它一样。每次订阅一个 observable 是一件痛苦的事情。
AuthService.ts
getAuthToken():string {
return localeStarage.getItem('token')
}
然后你可以这样做:
const token = this.authService.getAuthToken();
我想这里发生的事情是,当订阅 observable 时,authState 的值已经在登录过程中发出,因此请求挂起等待发出新值,这已经发生了在登录过程中。
为了解决这个问题,我建议你实现一个服务(providedInRoot)注入到登录组件中,并在登录过程中检索用户数据。
您可以在 Login 组件的 OnInit 中订阅 SocialAuthService 服务的 authState observable:
import { Component, OnInit, OnDestroy } from '@angular/core';
import { SocialAuthService } from "angularx-social-login";
import { Subject } from 'rxjs';
import { takeUntil } from 'rxjs/operators';
// ... the rest of import
@Component({
// ... Component decorator props (selector, templateUrl, styleUrls)
})
export class LoginComponent implements OnInit, OnDestroy {
destroy = new Subject<boolean>();
constructor(private authService: SocialAuthService, myUserService: MyUserService) { }
ngOnInit(): void {
this.authService.authState.pipe(takeUntil(this.destroy)).subscribe((user) => {
this.myUserService.user = user;
});
}
// ... the rest of the component
ngOnDestroy(): void {
this.destroy.next(true);
this.destroy.complete();
}
}
然后您可以在拦截器中使用 myUserService.user
中的值来检索令牌。
我已经将 takeUntil rxjs 运算符与 ngOnDestroy 的主题一起使用,但您也可以将订阅存储为 class 变量并执行取消订阅。
import { Component, OnInit, OnDestroy } from '@angular/core';
import { SocialAuthService } from "angularx-social-login";
import { Subscription } from 'rxjs';
// ... the rest of import
@Component({
// ... Component decorator props (selector, templateUrl, styleUrls)
})
export class LoginComponent implements OnInit, OnDestroy {
authStateSubscription: Subscription;
constructor(private authService: SocialAuthService, myUserService: MyUserService) { }
ngOnInit(): void {
this.authStateSubscription = this.authService.authState.subscribe((user) => {
this.myUserService.user = user;
});
}
// ... the rest of the component
ngOnDestroy(): void {
this.authStateSubscription.unsubscribe();
}
}
两种方式都应该有效。
我倾向于同意@cybering 的解释,要添加到他的解决方案中,您可以将 authState
定义为 BehaviorSubject。这样,无论某个组件何时订阅它,它都会获得一个发射值,因为 BehaviorSubject“保存”最后发射的值并在新订阅时发射它而不依赖于时间。