2

In my Angular Project, I'm trying to have a sign in function that signs the user in with google and then updates their user data dependent on whether or not they already have the approved role.

When trying to make this work, neither of my console.log statements fire so I'm not sure what I can be doing to get into those blocks.

async handleSignIn() {
    const credential = await this.afAuth.signInWithPopup(new firebase.default.auth.GoogleAuthProvider());
    return this.manageUserData(credential.user)
}

private async manageUserData(user: User) {
    let data$; // Init Variable
    let update$; // Init Variable
    const userCollection = this.afs.collection<User[]>('users'); // Sets Path for Data
     console.log(user.uid);
    data$ = userCollection.doc<User>(user.uid).snapshotChanges().pipe( // Fetches Document
        map(action => { // Maps To Be Able To Do Things
            const payload = action.payload.data(); // Sets Payload Data as the main `payload` variable
            console.log("In place", payload)
            if(payload.roles.approved == true || false) {
                console.log("main block")
                update$ = {
                    uid: user.uid,
                    email: user.email,
                    displayName: user.displayName,
                    photoURL: user.photoURL
                }
            } else {
                console.log("else block")
                update$ = {
                    uid: user.uid,
                    email: user.email,
                    displayName: user.displayName,
                    photoURL: user.photoURL,
                    roles: {
                        approved: false
                    }
                }
            }
            const userRef: AngularFirestoreDocument<User> = this.afs.doc(`users/${user.uid}`);
            return userRef.set(update$, { merge: true });
        })
    )
}

The console.log(user.uid) statement returns my firebase UID: rKlGL943eqfaPVWLh2EEUVBoOvg2

Edit: I also tried using the ternary operator here and organizing functions better:

function hasApprovedStatus() {
      update$ = {
        uid: user.uid,
        email: user.email,
        displayName: user.displayName,
        photoURL: user.photoURL
      }
      userRef.set(update$, { merge: true });
    }
    function hasNoApprovedStatus() {
      update$ = {
        uid: user.uid,
        email: user.email,
        displayName: user.displayName,
        photoURL: user.photoURL,
        roles: {
          approved: false
        }
      }
      userRef.set(update$, { merge: true })
    }
data$ = userCollection.doc<User>(user.uid).snapshotChanges().pipe( // Fetches Document
      map(action => action.payload.data().roles.approved ? hasApprovedStatus() : hasNoApprovedStatus())
    )
  }
11
  • Can you add a console.log(user.uid) right before you use it, and include the updated code and its output in your question? Please also include a screenshot of the document that you are trying to load for this user. Commented Dec 26, 2020 at 15:44
  • The document I'm trying to load doesn't exist, so what I'm trying to detect is if it doesn't exist, go into the else block and set that path to it, and if it does exist, then leave the roles part of it. Commented Dec 26, 2020 at 15:58
  • I'll do the console.log and update the post though. Commented Dec 26, 2020 at 15:58
  • @FrankvanPuffelen Edit has been made Commented Dec 26, 2020 at 16:15
  • Wouldn't the map(action => only be invoked if there is a document in the result? Since you don't have a matching document, I'm not sure it'll run. The easiest way to check that is to see if the console.log("In place", payload) is showing in the output. Commented Dec 26, 2020 at 17:09

1 Answer 1

1

The problem could be that your async method manageUserData() is not actually returning anything.

The method is creating an Observable from userCollection.doc<User>(user.uid).snapshotChanges() and assigning it to local var data$ but it's not returning that from the method, and it's also not subscribing to it. So you appear to be creating an Observable that never gets subscribed to, and therefore your map function will never be invoked.

What you need to do is return the Observable you're creating (also, no need to assign it to a local var unless you're going to use that var for something):

return userCollection.doc<User>(user.uid).snapshotChanges().pipe(...

That will cause manageUserData() to return a Promise (since it's async) that resolves to an Observable.

Since handleSignIn() returns the return value of manageUserData(), that Promise will be passed along to whoever calls handleSignIn(). That calling method would look something like this:

async signIn() {
  const handle: Observable<any> = await this.handleSignIn();

  handle.subscribe();
}

Since handleSignIn() returns a Promise, we must await that Promise's resolution and then subscribe to the resulting Observable (i.e., your snapshotChanges()).

Unless you need this to be Promise-based, you might consider simply returning the Observable from manageUserData() and handleSignIn() instead of wrapping it in a Promise (by making those methods async), then you wouldn't need the extra step of awaiting the Promise to resolve.

Here's a StackBlitz with a mocked version of this chain of method calls working correctly.

Update in response to Comments, 12/29/2020:

If you need to filter your stream so that the first line in your map function, const payload = action.payload.data(), will always retrieve a value from method call action.payload.data(), you can add a filter operator into your pipe before the map:

return userCollection.doc<User>(user.uid).snapshotChanges().pipe(
  filter(action => Boolean(action.payload?.data?.())),
  map(action => { ... )
)

A couple other observations:

  1. The first line in your map function is
const payload = action.payload.data();

in which you're invoking the data() method on the payload property. Is data() truly a method or is it just a property?

  1. The third line in your map function is
if(payload.roles.approved == true || false) { ...

That's a strangely constructed condition. What condition(s) are you testing for?

The || false essentially does nothing because it's only evaluated if payload.roles.approved == true is false, so you'd just be checking if (false || false), which doesn't seem very useful.

If your intention was to check if payload.roles.approved is either strictly true or strictly false, then you would do this (note that I'm using strict equality, ===, rather than loose equality because that's preferred unless there's a good reason to use loose equality):

if(payload.roles.approved === true || payload.roles.approved === false) { ...
Sign up to request clarification or add additional context in comments.

3 Comments

The problem I'm having with that is when I do the handle.subscribe(); it only outputs the In place undefined (the "In place` comes from the string part of that, and it says that the payload is == undefined). And then I get an error saying TypeError: Cannot read property 'roles' of undefined. So while this helps me get into that pipe, it doesn't get me into the if or else block, which is where I need to be at.
That's because the observable stream is not emitting what you are expecting it to. TypeError: Cannot read property 'roles' of undefined means that your payload on the action is undefined. Perhaps not all actions will have a payload? In that case, add a filter(action => !!action.payload) to your stream. This will prevent any actions (values) in the stream from emitting if there is no payload property on them.
Where should that be added exactly? I tried adding it to this statement: map(action => {... and it gave me a bunch of errors so I don't think I did it in the right spot. @Jusmpty

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.