0
votes

Problem: I need to route to the login page if firebase-auth does not have a user signed in and to an alternate page if it does have a user logged in.

To achieve this it would invoke a function in a constructor service.

private isLoginRequired() {
// Firebase auth service observable
let authSub = this.afAuth.authState;

// Current route service observable
let routeSub = this.activatedRoute.url;

//Alternate App Page
//this.router.navigate(["tool/event-management/events"]);

//Login Page
//this.router.navigate(["tool/event-management/login"]);

}

Now, I need to combine these observables in such a way that if a value of any one of them changes I could collect current values of both the observables via single subscribe.

something.subscribe(observableSnapshots => {
  let snapshot_auth = observableSnapshots[0];
  let snapshot_route = observableSnapshots[1];

  if(condition<snapshot_route, snapshot_auth>){
    // do anything positive
  }
  else{
    // do anything negative
  }
});

I understand that this can be achieved by nesting the subscriptions but that's surely not an efficient way to do it.

I have also tried mergeMap but could not achieve the requirement, here is the snippet (which does not work)

routeSub
      .pipe(mergeMap(event1 => authSub.pipe(map(event2 => "test"))))
      .subscribe(result => {
        console.log(result);
      });

Please show me a better way out. Thanks in advance.

Dependency: "rxjs": "~6.3.3"

3
You shouldn’t subscribe to your inner obs but to your outer..MikeOne
@MikeOne I have tried swapping them around wasn't helping much.Abhijit Srivastava
Have you looked into router guards in Angular? They can handle the auth logic before the user lands on the page.sketchthat
@sketchthat, What if the user is already on the page, and event to lock that page(such as logout) is triggered from a second tab?Abhijit Srivastava
Here is an article that shows a visualization of ways you can merge observables in Angular. Its worth a look. Learn to combine RxJs sequences with super intuitive interactive diagramsttugates

3 Answers

2
votes

I would use a route guard to do what you are describing:

This is a slightly modified code snippet from Jeff Delaney's AngularFirebase website, see: https://angularfirebase.com/lessons/google-user-auth-with-firestore-custom-data/:

import { Injectable } from '@angular/core';
import { CanActivate, ActivatedRouteSnapshot, RouterStateSnapshot, Router } from '@angular/router';
import { AngularFireAuth } from '@angular/fire/auth';
import { Observable } from 'rxjs';
import { tap, map, take } from 'rxjs/operators';

@Injectable()
export class AuthGuard implements CanActivate {
  constructor(private auth: AngularFireAuth, private router: Router)
{}


  canActivate(
    next: ActivatedRouteSnapshot,
    state: RouterStateSnapshot): Observable<boolean> {

      return this.auth.authState.pipe()
           take(1),
           map(user => !!user),
           tap(loggedIn => {
             if (!loggedIn) {
               console.log('access denied')
               this.router.navigate(['/login']);
             }
         })
    )
  }
}

Here is how to add the guard to the router:

const routes: Routes = [
  { path: '**', component: SomeComponent, canActivate: [AuthGuard]},
]

You can import the Firebase auth observable in the guard as I have done here, however, it is better practice to have it in its own service imo. Check the link to AngularFirebase to see the code for a service that does this.

If you want to throw the user out of a route if they are logged out, try the following code in your component:

export class SomeComponent implements OnInit {
        constructor(private auth: AngularFireAuth, private router: Router)
{
    ngOnInit(): void {
        this.auth.authState.subscribe((authObj) => {
            if(authObj === null){
                this.router.navigate(['/login']);
            }
        })
    }
}
1
votes

I'm not 100% sure what you're doing, but it sounds like combineLatest might work

// Firebase auth service observable
let authSub = this.afAuth.authState;

// Current route service observable
let routeSub = this.activatedRoute.url;

const sub = combineLatest(authSub, routeSub)
   .pipe(
     tap(([authResult, routeResult]) => {
         // the above uses array param destructuring, so we have two variables
         if(authResult === something and routeResult === something){
            //Do Something
         } 
         else {
           // do something else
         }
     })
   )
   .subscribe();

The activatedRoute.url is an observable of a BehaviorSubject so it will give you the latest result when you subscribe, and I presume the Firebase Auth service will so the same, but you'll have to test it out

There may be syntax errors in this, just typed it directly, but it's your basic combineLatest

0
votes

Finally, I arrived at this code snippet with the help @Drenai response.

private isLoginRequired():void {
let authSub: Observable<firebase.User> = this.afAuth.authState;
let routeSub: Observable<any> = this.router.events;

combineLatest(authSub, routeSub).subscribe(
  ([authSubSnap, routeSubSnap]):void => {
    let islogin: boolean = !!authSubSnap;
    let current_url: string = routeSubSnap.url;

    if (!islogin) {
      this.router.navigate([this.loginPageURL]);
    } else if (current_url === this.loginPageURL && islogin) {
      this.router.navigate(["tool/event-management/events"]);
    }
  }
);
}