简体   繁体   中英

Combine RxJS Observable in Angular

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"

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']);
            }
        })
    }
}

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

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"]);
    }
  }
);
}

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM