I'm building an authentication app using the PEAN stack (i.e., PostgreSQL - ExpressJS - Angular - NodeJS).
I check for user sign-in status as follows:
- On the backend, check the session cookie to see if the userproperty exists in thereq.sessionobject.
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' });
  }
});
/* ... */
- Send an HTTP POST request to the api/get-signin-statusendpoint with optional data and include a cookie in the request.
auth.service.ts
/* ... */
getSignInStatus(data?: any) {
  return this.http.post(this.authUrl + 'api/get-signin-status', data, {
    withCredentials: true,
  });
}
/* ... */
- Intercept any HTTP request and provide an observable interceptorResponse$for subscribing to the response of intercepted requests.
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) {}
}
- On the frontend, subscribe to the interceptorResponseobservable from theInterceptorServiceand log the response to the console.
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 {}
}
Problem
According to the StackOverflow answer, I should use BehaviorSubject. The problem is that in the console, I always get the following:
But if I console log next and error like this:
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 });
  },
};
/* ... */
I see the expected {message: 'User logged in'} in the console, as shown in the screenshot below. This means that the backend correctly passes sign-in status to the frontend.
Question
Why does the Angular interceptor always return null (i.e., the initial value) using BehaviorSubject, not the updated value?
EDIT 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 {}
EDIT 2
With the help of @VonC I managed to get the whole thing working as expected. Here's what I did.
- I removed my initial code in server.jsbecause the interceptor will now depend on theapi/get-userendpoint, notapi/get-signin-statuslike before. Consequently, I don't needapp.post('/api/get-signin-status', () => {})anymore. The reason why I now use theapi/get-userendpoint is because both did the same thing (i.e., checked the session cookie to see if theuserproperty exists in thereq.sessionobject), which means that only one is enough for my auth app. There's no need to check the session cookie twice.
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' });
  }
});
*/
/* ... */
- I removed my initial code in auth.service.tsand added the code as @VonC suggested.
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);
}
/* ... */
- I removed my initial code in interceptor.service.tsand added the code as @VonC suggested. Note: I changed the endpoint fromapi/get-signin-statustoapi/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) {}
}
- I removed my initial code in header.component.tsand added the code as @VonC suggested.
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 {}
}
Now I can finally show elements in header.component.html depending on sign-in status coming from the backend as follows:
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>


 
     
    