我正在构建一个类似于此的角度架构: https ://blog.angular-university.io/how-to-build-angular2-apps-using-rxjs-observable-data-services-pitfalls-to-avoid/
我只有一个用于管理状态的基类,它管理特定模块的状态:
import { BehaviorSubject, Observable } from 'rxjs';
import { distinctUntilChanged, map, take } from 'rxjs/operators';
export class StateService<T> {
private state$: BehaviorSubject<T>;
protected get state(): T {
return this.state$.value;
}
constructor(initialState: T) {
this.state$ = new BehaviorSubject<T>(initialState);
}
protected select<K>(mapFn: (state: T) => K): Observable<K> {
return this.state$.asObservable().pipe(
map((state: T) => mapFn(state)),
);
}
protected clearState(){
this.state$.next(null as any);
}
protected setState(newState: Partial<T>) {
this.state$.next({
...this.state,
...newState,
});
}
}
所以为了管理状态,你只需要简单地扩展这个类
import { HttpClient, HttpErrorResponse, HttpHeaders } from "@angular/common/http";
import { Injectable } from "@angular/core";
import { Observable, ObservableInput, throwError } from "rxjs";
import { catchError, shareReplay, tap } from 'rxjs/operators';
import { StateService } from "src/app/core/services/state-service.service";
import { AuthHttpClient } from "./auth-httpclient.service";
import { UserInfo } from "./state-models/user-info.model"
@Injectable({
providedIn: 'root'
})
export class AuthService extends StateService<UserInfo>{
protected _tokenExpirationTimer:any;
protected tokenExpirationDuration = 3600;
public userInfoattempt$ : Observable<UserInfo> = this.select(state => state);
public oauthTriggerState$ : Observable<boolean> = this.select(state => state.oauthTriggerSignup);
constructor(){
super(null as any);
}
public autoLogout(expirationDuration:number){
this._tokenExpirationTimer = setTimeout(() => {
this.logoutUser();
}, expirationDuration * 1000);
}
public logoutUser(){
this.clearState();
localStorage.clear();
if(this._tokenExpirationTimer)
clearTimeout(this._tokenExpirationTimer);
this._tokenExpirationTimer = null;
}
protected handleError(errorResp:HttpErrorResponse):ObservableInput<any>{
if(errorResp.error.errors) {
console.log("Error:",errorResp);
return throwError(errorResp.error.errors);
}
return undefined as unknown as ObservableInput<any>;
}
public loginUser(userCredentials:UserCredentials,provider?:string) {
return this.api.request<UserSignedinData,UserSignedinDataAdd>( provider ? `api/auth/signin?provider=${provider}` : 'api/auth/signin','post',null as any,userCredentials)
.pipe(
tap(val => {
if(val.isSuccessfull){
var decodedToken : Partial<UserInfo> = jwt_decode(val.data.Token);
var user = new UserInfo(
val.data.Token,
decodedToken.id,
decodedToken.email,
decodedToken.exp,
decodedToken.username,
decodedToken.phone
);
this.setState(user);
// add to local storage
localStorage.setItem("user",JSON.stringify(user));
//session expiry
this.autoLogout(this.tokenExpirationDuration);
}
else{
throw new Error("Some Error Occured!");
}
//temporarily throw error
}),
catchError(this.handleError)
)
}
}
这里感兴趣的方法是 loginUser,正如您在 tap 运算符中看到的那样,它基本上调用 setState 方法,该方法在父级中的 behviourSubject 上调用 .next(),并且显然新值反映给 userInfoattempt$ 可观察的所有订阅者,所以当我在一个组件中注入这个服务并订阅 userInfoattempt$ 时,只要有变化,我就会得到新的值,这工作正常,直到我让另一个服务说 signinService 扩展这个 authService 并且我把登录方法放在那里,
import { HttpClient } from '@angular/common/http';
import { Injectable } from '@angular/core';
import { catchError, shareReplay, tap } from 'rxjs/operators';
import { AuthHttpClient } from '../auth-httpclient.service';
import { AuthService } from '../auth.service';
import { UserCredentials } from '../state-models/user-credentials.model';
import { UserInfo } from '../state-models/user-info.model';
import { UserSignedinDataAdd } from '../state-models/user-signedin-data-add.model';
import { UserSignedinData } from '../state-models/user-signedin-data.model';
import jwt_decode from 'jwt-decode';
import { UserSignedupData } from '../state-models/user-signedup-data.model';
import { UserSignupCredentials } from '../state-models/user-signup-credentials.model';
import { UserSingedupDataAdd } from '../state-models/user-singedupdataadd.model';
import { Observable } from 'rxjs';
@Injectable({
providedIn: 'root'
})
export class SigninService extends AuthService {
public userInfo$ : Observable<UserInfo> = this.select(state => state);
constructor(private api:AuthHttpClient,private http:HttpClient) {
super();
}
public loginUser(userCredentials:UserCredentials,provider?:string) {
return this.api.request<UserSignedinData,UserSignedinDataAdd>( provider ? `api/auth/signin?provider=${provider}` : 'api/auth/signin','post',null as any,userCredentials)
.pipe(
tap(val => {
if(val.isSuccessfull){
var decodedToken : Partial<UserInfo> = jwt_decode(val.data.Token);
var user = new UserInfo(
val.data.Token,
decodedToken.id,
decodedToken.email,
decodedToken.exp,
decodedToken.username,
decodedToken.phone
);
this.setState(user);
// add to local storage
localStorage.setItem("user",JSON.stringify(user));
//session expiry
this.autoLogout(this.tokenExpirationDuration);
}
else{
throw new Error("Some Error Occured!");
}
//temporarily throw error
}),
catchError(this.handleError)
)
}
public autoLogin(){
const user = JSON.parse(localStorage.getItem('user')!) as UserInfo;
if(!user)
return;
this.setState(user);
var remainingTime = this.tokenExpirationDuration;
this.autoLogout(remainingTime);
}
}
这就是问题发生的地方,这里的 loginUser 方法是相同的,但是当我向组件“注入 authService(不是 signinService)”并订阅 userInfoattempt$ 时,如果我将 signinService 注入组件和订阅 userInfo$ 会反映新值,尽管它们都指向同一个 BehaviourSubject,但我在这里遗漏了什么吗?