Display Name
Display Name

Reputation: 130

Unable to Update User Data Using Function

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())
    )
  }

Upvotes: 2

Views: 276

Answers (1)

MikeJ
MikeJ

Reputation: 2324

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) { ...

Upvotes: 1

Related Questions