Angular - 如何统一auth observable?

Angular - How to unify auth observable?

我有一个 Angular 服务,它使用 AngularFire 的 auth observable 来监听用户的状态变化。当用户登录时,应用程序必须从 MongoDB 获取用户文档。这些数据需要被组件使用,所以我需要有另一个可观察的。问题是,我不太确定如何让它工作。

这是我的身份验证服务的片段。

import { Injectable } from '@angular/core';
import { AngularFireAuth } from '@angular/fire/auth';
import { Router } from '@angular/router';
import firebase from 'firebase/app';
import { environment } from '../../environments/environment'
import { HttpClient, HttpHeaders } from '@angular/common/http';
import { Observable, of } from 'rxjs';
import { User } from '../interfaces/User.model'

@Injectable({
  providedIn: 'root'
})
export class AuthService {

  public redirectRoute = ''
  public loginError = '';
  public _mongoUser: Observable<User | null> = of(null);
  public mongoUser: User | null = null;

  constructor(public auth: AngularFireAuth, private router: Router, private http: HttpClient,) {

    this.auth.user.subscribe(async (user) => {
      console.log('auth changed', user)
      if (user) {

        let headers = {
          headers: new HttpHeaders()
            .set('idToken', await user.getIdToken())
        }

        this._mongoUser = this.http.post<User>(
          `${environment.apiUrl}/users/email/${user.email}`,
          { personal: { email: user.email } },
          headers
        )

        this._mongoUser.subscribe(val => {
          console.log('val', val)
          this.mongoUser = val
        })

      } else {

      }
    })
  }

}

主要问题是,我应该如何初始化_mongoUser?我认为使用...然后 httpClient 方法无法按我希望的方式工作。

我想在其他组件中像这样使用_mongoUser 或mongoUser。但是,我上面的代码不起作用。

constructor() {
    this.authService._mongoUser.subscribe(val => {    
      if (val) {
        this.editForm.patchValue({ 'username': val.username })
      }

    })
 }

通过重新分配给 this._mongoUser,您将丢弃在重新分配之前进行的所有订阅。

为避免重新分配,您可以使用 SubjectBehaviorSubject。在这种情况下,我认为 BehaviorSubject 会更合适。它跟踪最近发出的项目和

  1. 向新订阅者重新发布该项目
  2. lets you synchronously access that item with the BehaviorSubject#getValue method

这是您的示例的一个版本,已修改为使用 BehaviorSubject

import { Injectable } from '@angular/core';
import { AngularFireAuth } from '@angular/fire/auth';
import { Router } from '@angular/router';
import firebase from 'firebase/app';
import { environment } from '../../environments/environment'
import { HttpClient, HttpHeaders } from '@angular/common/http';
import { Observable, of, BehaviorSubject } from 'rxjs';
import { tap, filter, mergeMap } from 'rxjs/operators';
import { User } from '../interfaces/User.model'

@Injectable({
  providedIn: 'root'
})
export class AuthService {

  public redirectRoute = ''
  public loginError = '';

  // readonly so we never reassign this field
  public readonly mongoUser: BehaviorSubject<User | null> = new BehaviorSubject(null);

  constructor(public auth: AngularFireAuth, private router: Router, private http: HttpClient,) {
    this.auth.user.pipe(
      tap((user) => console.log('auth changed', user)),
      filter((user) => !!user),
      mergeMap(async (user) => ({ user, idToken: await user.getIdToken()})),
      mergeMap(({user, idToken}) => {
        let headers = {
          headers: new HttpHeaders()
            .set('idToken', idToken)
        }

        return this.http.post<User>(
          `${environment.apiUrl}/users/email/${user.email}`,
          { personal: { email: user.email } },
          headers
        )
      }),
    ).subscribe({
        next: (userFromApi) => this.mongoUser.next(userFromApi)
    });
  }

}

重新分配对象

在 Javascript 中,如果您有一个对象的引用并且它被重新分配,您不会获得新的引用。

let exampleObject = {Hello: "World"};
const ref = exampleObject;
exampleObject = {New: "Value"};

console.log("ref: ", ref); // ref: {"Hello":"World"}
console.log("eo : ", exampleObject); // eo : {"New":"Value"}

所以您不想重新分配 public _mongoUser 因为其他服务可能有过时的引用。

您可以改用 behaviorSubject:

@Injectable({
  providedIn: 'root'
})
export class AuthService {

  public redirectRoute = ''
  public loginError = '';
  public _mongoUser = new BehaviorSubject<User | null>(null);
  public mongoUser: User | null = null;

  constructor(public auth: AngularFireAuth, private router: Router, private http: HttpClient,) {

    this.auth.user.pipe(
      map(user => user.getIdToken().pipe(
        map(idToken => ({user, idToken}))
      ))
    ).subscribe(({user, idToken}) => {
      console.log('auth changed', user)
      if (user) {

        let headers = {
          headers: new HttpHeaders()
            .set('idToken', idToken)
        }

        this.http.post<User>(
          `${environment.apiUrl}/users/email/${user.email}`,
          { personal: { email: user.email } },
          headers
        ).subscribe(this._mongoUser.next.bind(this._mongoUser));

        this._mongoUser.subscribe(val => {
          console.log('val', val)
          this.mongoUser = val
        })

      } else {

      }
    })
  }

}

一些清理工作

以上是应该开始的最小更改,但有几个地方您可以使用 RxJS 进一步简化您的代码:

@Injectable({
  providedIn: 'root'
})
export class AuthService {

  public redirectRoute = ''
  public loginError = '';
  public _mongoUser = new BehaviorSubject<User | null>(null);
  public mongoUser: User | null = null;

  constructor(public auth: AngularFireAuth, private router: Router, private http: HttpClient,) {
    
    this._mongoUser.subscribe(val => {
      console.log('val', val)
      this.mongoUser = val
    });

    this.auth.user.pipe(
      filter(user => user != null),
      map(user => user.getIdToken().pipe(
        map(idToken => ({
          user, 
          headers: { 
            headers: new HttpHeaders().set('idToken', idToken) 
          }
        }))
      )),
      concatMap(({user, headers}) => this.http.post<User>(
        `${environment.apiUrl}/users/email/${user.email}`,
        { personal: { email: user.email } },
        headers
      ))
    ).subscribe(
      this._mongoUser.next.bind(this._mongoUser)
    );
  }
  
}