I have an Angular service that uses AngularFire's auth observable to listen to state changes of the user. When a user signs in, the app must fetch a user document from MongoDB. This data needs to be consumable by components, so I need to have another observable. The problem is, I'm not quite sure how to make it work.
Here is a snippet of my auth service.
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 {
}
})
}
}
The main question is, how should I initialize _mongoUser? I think using of... and then the httpClient method is not working the way I want it to.
I want to consume _mongoUser or mongoUser like this in other components. However, my code above does not work.
constructor() {
this.authService._mongoUser.subscribe(val => {
if (val) {
this.editForm.patchValue({ 'username': val.username })
}
})
}
By reassigning to this._mongoUser
, you are throwing away all of the subscriptions that were made before the reassignment.
To avoid reassignment, you can use a Subject
or BehaviorSubject
. In this case, I think BehaviorSubject
would be a better fit. It keeps track of the most recently emitted item and
BehaviorSubject#getValue
methodHere is a version of your example that is modified to use 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)
});
}
}