Home > front end >  How to get a Service to to run upon refreshing the page?
How to get a Service to to run upon refreshing the page?

Time:11-13

I am working with Angular and Firebase through the angular/angularfire library. I have two services: HandleAuthService which manages the authentication of users and CrowboxdbService which manages the RealTime Database. Currently, in my Home page component, I am checking if a user is logged in. If they are logged in, they may access the other site pages. If they are not logged in, I provide them with a button to sign up or sign in. Upon doing so, they are redirected to another page where they may view their data (in data.component.ts).

I have no issues when the user first signs up or first logs in. All my components can access the authState from the HandleAuthService and therefore access things like the User's Name and the uid. I use the uid to update the user's data within the RTDB.

The issue I am coming across arises when the page refreshes. When the page refreshes, it seems that HandleAuthService runs a little later than everything else. So the components cannot retrieve the relevant information such as the uid.

Here are snippets of code from each of the files:

app.module.ts: Here, I have set the HandleAuthService as a provider:

//imported the service (as well as in the angular import)
import { HandleAuthService } from './services/shared/handle-auth.service';
//set it as a provider for the other components
providers: [HandleAuthService]

HandleAuthService

  constructor(private fireAuth: AngularFireAuth, private ngZone: NgZone, private router: Router) { 
    /* initialise the currentUserId by retrieving it from the authState */

    this.fireAuth.authState.subscribe(user => {
      if (user) {
        console.log("User is LOGGED IN");
        this.currentUserState = {
          uid: user.uid!,
          email: user.email!,
          displayName: user.displayName!
        };

        console.log("Current User State is:");
        console.log(this.currentUserState);
        //set the object in the localstorage
        localStorage.setItem('user', JSON.stringify(this.currentUserState));
      } else {
        console.log("User is not logged in?");
        localStorage.setItem('user', "null");
      }
    })
  }

  login() {
    /* Sign in or Sign Up with google's pop up.*/
    return this.googleLogin( new firebase.auth.GoogleAuthProvider());
  }


  googleLogin(provider:any) {
    return this.fireAuth.signInWithPopup(provider)
    .then((result)=> {
      this.ngZone.run(() => {
        this.router.navigate(['data']);
      });
      this.setUser(result.user);
    })
    .catch((error) => {
      console.log(error);
    })
  }

  setUser(result:any) {
    this.currentUserState = {
      uid : result.uid,
      email : result.email,
      displayName : result.displayName
    };
  }

Now, in data.component.ts, where I call the HandleAuthService and make use of it, it only works as long as the page has not been refreshed.

constructor(private authService: HandleAuthService, private crowboxService: CrowboxdbService) {}
  
ngOnInit(): void {
    //upon the view rendering, get the User Id 
    this.currentUserId = this.authService.currentUserState?.uid;
    console.log("Current User Id is " );
    console.log(this.currentUserId);

    this.checkIfUserExists();
  }

In ngOnInit(), I attempt to print out the user ID. If the page has been refreshed, it returns undefined. If, however, the user navigates to this page directly after logging in (or from the home page), it works. Why is that? How can I ensure that HandleAuthService is always the first thing to be run?

The same issue is faced in CrowboxdbService In this service, to push/update/read values from the RTDB, I need the user ID. When the user first logs in, I can get the user ID from the HandleAuthService. However, upon refreshing the page, this service cannot get the ID from the HandleAuthService and instead resorts to getting the ID from the local storage. I do not want to do this, however, as it does not help with the restricted auth rules that have been set up in firebase.


  constructor(private db: AngularFireDatabase, private handleAuth: HandleAuthService) {
     
    //try to get the user id from handleAuth (if this is the first time loggin in)
    this.currentUserId = this.handleAuth.currentUserState?.uid;
  }

    //if you cannot get the user id from handleAuth, then get it from the localStorage
    if(!this.currentUserId) {
      console.log("Error in crowboxdb Service - Cannot retrieve user id from handleAuth");
      //get the user information from the local storage
      const item = localStorage.getItem('user');
      if (item !=='undefined' && item!==null) {
        const currentUser = JSON.parse(item);
        this.currentUserId = currentUser.uid!;
      }
    }

CodePudding user response:

Yes, this is asynchronous, so it takes a while for the user to be set after refresh. This is just how it works and try to embrace it :)

What I usually do is to assign the value to an observable, there is no need for localStorage either as I see it, firebase will always emit to you, that is the beauty of it. You always get the user from authstate if it exists. So I would as said assign to an observable and subscribe to it in components. if you attach take(1), you don't need to unsubscribe, it is only emitted once. So service:

 currentUser$ = this.fireAuth.authState.pipe(
   map(user => {
     if (user) {
       return { uid: user.uid,  email: user.email, displayName: user.displayName }
     }
     return null;
   })
 );    

Now everywhere you need this user info just subscribe to it:

ngOnInit(): void {
  this.authService.currentUser$.subscribe(user => console.log(user))
}

Better yet if you are using this user info in template, instead of using subscribe, use the async pipe. Also I suggest you to type your data, angularfire has their own types, but you can also write your own interfaces. It is worth it, it will help you in the future! :)

  • Related