Connecting AngularFirebaseAuth: Running server API immediately following Firebase authentication?

My authentication process relies on two key factors:

  • Using Firebase auth (email and password)
  • Making a server API call to retrieve the complete customer entity from the database based on the firebaseID. The user must exist in both places for successful authentication.

I have implemented authGuards that are based on an isAuthenticated() function returning an Observable. This is necessary because during page refreshes, the guard needs to wait for the authentication process to finish before redirecting the user.

The issue I am facing is with managing all the asynchronous calls and RxJS operations. Currently, the serverAPI auth is called every time the isAuthenticated function is invoked. How can I refactor this to ensure that the server call is made only once while still maintaining the async behavior and handling reloads effectively?

AuthService :

paste here AuthService code...

AuthGuard :

paste here AuthGuard code...

Thank you

Answer №1

If you want to update your checkProfile() function to return an observable instead of the one from a http request or promise when encountering an error, you can do so by first checking if the user is already authenticated (assuming that the userRole is valid since it's saved after the call to the backend). If they are authenticated, you can simply return a newly created observable without making another call to the backend. However, if the user is not yet authenticated, then you will proceed with making the request and emitting your observable based on the result of the HTTP call. The following example demonstrates how you can achieve this in a way that only requires making the call once:

checkProfile() {
  return new Observable((observer) => {
    if (this.userRole) {
      observer.next();
      observer.complete();
    } else {
      this.callAuthApi().pipe(
          map((customer) => {
            if (!customer || customer.hasRole() === "anonymous") {
              observer.error(new Error(AuthService.AUTH_ERROR_ROLE));
              observer.complete();
            }
            this.userRole = customer.getRole();
            observer.next();
            observer.complete();
          })
      );
    }
  });
}

Answer №2

ReactiveX can be a bit challenging to grasp at first with its steep learning curve, but it packs a punch in terms of power.

1. Making a single server call

Utilize shareReplay for this purpose.

To understand the workings of shareReplay better, check out this example

//shareReplay example
ngOnInit() {    
    const tods$ = this.getTodos();
    tods$.subscribe(console.log);// 1st sub
    tods$.subscribe(console.log);// 2st sub
}

getTodos(): Observable<Todo[]> {
return this.http.get<Todo[]>(this.url)
  .pipe(
    tap(() => console.log('Request')),
    shareReplay(1) // compare with comment and uncomment
  );
}  

Output with shareReplay

Request
[Object, Object, Object]
[Object, Object, Object]

Output without shareReplay

Request
[Object, Object, Object]
Request
[Object, Object, Object]

Implementing shareReplay in your auth service code is beneficial.

//auth.services.ts
import { shareReplay } from 'rxjs/operators';
...

this.user$ = this.afAuth.authState.pipe(
    tap(user => {
        console.log('login user$ here', user)
    }),
    switchMap(user => {
        if (user) {
            //do something
            return this.db.object(`users/${user.uid}`).valueChanges();
        } else {
            return of(null);
        }
    }),
    shareReplay(1)  //**** this will prevent unnecessary request****
);

2. Using async and await toPromise()

//auth.service.ts
...
getUser() {
    return this.user$.pipe(first()).toPromise();
}

//auth.guard.ts
...
async canActivate(next: ActivatedRouteSnapshot
  , state: RouterStateSnapshot
): Promise<boolean> {

  const user = await this.auth.getUser();
  //TODO your API code or other conditional authentication here

  if (!user) {
    this.router.navigate(['/login']);
  }
  return !!user;    
}

I hope this information proves helpful!

Similar questions

If you have not found the answer to your question or you are interested in this topic, then look at other similar questions below or use the search

What is the best way to utilize AJAX for displaying data within a div element?

I am struggling with integrating two files - index.php and process.php. In my index.php file, there is a form that submits to process.php: <form class="form" action="process.php" method="POST" name="checkaddress" id="checkaddress"> <table> ...

Check to see if one string is beginning to resemble another string

Within an array, I have stored a set of keywords. When a user correctly types one of these keywords, the pass() function is executed. This validation occurs during each keystroke event (on key up), as I compare the user input to the items in the array. To ...

Using Vue Js, I utilized Axios to make a call within a function, receiving and storing the retrieved data into an array

While working inside the function shown in the screenshot, I am encountering an issue when trying to access the data retrieved from the backend using axios.get. After exiting the axios block, the values of the array appear as undefined when I attempt to pr ...

Unable to save text to file in both Javascript and PHP

I'm facing an issue with my website signup form. It consists of fields for email and password. The HTML triggers a JavaScript function which, in turn, calls PHP code to save the email to a file on the server. While the JavaScript seems to be functioni ...

Bring back enhanced assortment using Mongoose

I am currently working with a stack that includes nodejs, express, mongoose, and angularjs. My task involves updating a collection called Lists which contains various properties, including an array of items. The issue I am facing is that when I push a new ...

Having trouble resolving a missing dependency warning with the useEffect React Hook in my Next.js app. Any tips on how to fix this

Currently, I'm facing the following warning: Warning: React Hook useEffect has a missing dependency: 'router'. Either include it or remove the dependency array Here is the code snippet from _app.js that seems to be causing this issue: cons ...

Tips on sending form data, including a file, to Ajax using the onclick() method

My Modal Includes a Form: <div class="modal fade bs-example-modal-lg" id="myMODALTWO" tabindex="-1" role="dialog" aria-labelledby="myLargeModalLabel" id="form-content"> <div class="modal-dialog modal-lg" role="document"> ...

Clicking on the menu in mobile view will cause it to slide upward

I have implemented sticky.js on my website and it is working well. However, when I resize the browser to mobile view and click the main menu button, it goes up and I am unable to close it. I have to scroll up to see it again. How can I make it stick to the ...

sending a response to the web browser

Posting this partial code as the rest is functional. Just testing to see if what I want can be accomplished. Question: Is it feasible to redirect alert() errors to browsers instead of displaying them in a popup window? I aim to handle errors similarly t ...

Extracting object properties and tallying their occurrences

How can I extract a single property from an array of objects like the following: [{"name":"Bryan","id":016, "counter":0}, {"name":"John","id":04, "counter":2}, {"name":"Alicia","id":07, "counter":6}, {"name":"Jenny","id":015, "counter":9}, {"name":"Bryan" ...

Combining arrays to append to an array already in place

I have implemented the rss2json service to fetch an rss feed without pagination support. Instead of a page parameter, I can utilize the count parameter in my request. With this setup, I am successfully able to retrieve and display the feed using a service ...

"Utilizing GroupBy and Sum functions for data aggregation in Prisma

I am currently working with a Prisma schema designed for a MongoDB database model orders { id String @id @default(auto()) @map("_id") @db.ObjectId totalAmount Int createdAt DateTime @db.Date } My ...

Having trouble resolving all parameters for the service in an Angular2 test with Jasmine mocking

I am currently attempting to create a mock service for testing purposes: Production: @Injectable() export class UserService extends DtoService { // irrelevant details here. } @Injectable() export abstract class DtoService { constructor(private h ...

What potential outcomes arise from independently initiating multiple components simultaneously?

Here is a scenario where I am able to achieve the following: @NgModule({ imports: [BrowserModule], declarations: [AppComponent, BComponent], bootstrap: [AppComponent, BComponent] <---------- here two components }) By doing this, it will ge ...

Tips for indicating errors in fields that have not been "interacted with" when submitting

My Angular login uses a reactive form: public form = this.fb.group({ email: ['', [Validators.required, Validators.email]], name: ['', [Validators.required]], }); Upon clicking submit, the following actions are performed: ...

Is this code correct for passing a variable to another form?

$("#delete").click(function() { deleterecord(); }); function deleterecord(){ var id = $("#iduser").val(); alert("aw"+id); var id = $('#iduser').attr(); e.preventDefault(); pressed = "delete" $.ajax({ ...

Using d3 or ajax to read a local file containing tab-separated values may result in a syntax error appearing in the Firefox development console

The reading operation is functioning as expected. However, I encountered a syntax error in the Firefox console while going through multiple files, which can be quite tedious. These files are annotation files formatted like (time \t value) with no head ...

AngularFire: Retrieve the $value

Having an issue logging the service.title to the console as it keeps returning as a strange object. .factory('service', function($firebase, FBURL, $routeParams) { var ref = new Firebase(FBURL + "services/" + $routeParams.serviceId); var titl ...

Accessing the properties of a module in Javascript - a foolproof guide to making the most

In the constants.js file, I have defined the following code: var constants = ( conversationUsername: "user1", conversationPassword: "pass1", conversationVersionDate: "date1", conversationWorkspaceId: "work1" }; module.exports.constants ...

Only retrieve one result from v-for loop depending on the key value

My v-for loop is set up to display a single result based on the chosen :key value from the counter data property. Everything is working well, but I am encountering problems when I try to add <transitions> for a smoother update of the value. The issue ...