0

I am using the new Angular Firebase v.7 with Angular and I am getting an error: Cannot use 'in' operator to search for '_delegate' in users/1QAvZYg6aqe0GhA13tmVAINa.

There is a similar question ( Firebase Error: TypeError: Cannot use 'in' operator to search for '_delegate' in undefined ) but it is unanswered and I have been trying to little avail to find one myself. Perhaps I am overlooking something very simple.

The code

The service responsible for creating the document is in data.service.ts as looks like this:

import { Injectable } from '@angular/core';
import {
    collection, deleteDoc, doc, DocumentSnapshot, Firestore, onSnapshot,
    query, QuerySnapshot, setDoc, Timestamp as fTimestamp, Unsubscribe
} from '@angular/fire/firestore';
import { firstValueFrom, lastValueFrom, Observable, Subject } from 'rxjs';
import { take } from 'rxjs/operators';

export interface FirestoreExtDoc<T> {
    data: Observable<T>;
    unsubscribe: Unsubscribe;
}
export interface FirestoreExtCol<T> {
    data: Observable<T[]>;
    unsubscribe: Unsubscribe;
}

@Injectable({
  providedIn: 'root'
})
export class DataService {
  constructor(private db: Firestore) { }
    upsert<DocumentData>(ref: string, data: any): Promise<void> {
        const docRef = doc(this.db, ref);
        const timestamp = fTimestamp.now();
        const newData = {
            ...data,
            updatedAt: timestamp,
            createdAt: timestamp,
        };
        const updatedData = {
            ...data,
            updatedAt: timestamp,
        };
        const snapshot = lastValueFrom(this.getDoc<DocumentData>(ref).data.pipe(take(1)));
        return snapshot.then(
            snap => (snap as any).exists ?
                setDoc(docRef, updatedData, { merge: true }) :
                setDoc(docRef, newData, { merge: true })
            );
    };
}

In my auth.service.ts I import the above data service and call the upsert method like this:

import { Injectable } from '@angular/core';
import {
  Auth,
  signInWithEmailAndPassword,
  GoogleAuthProvider,
  authState,
  createUserWithEmailAndPassword,
  updateProfile,
  UserInfo,
  signInWithPopup,
  sendPasswordResetEmail
} from '@angular/fire/auth';
import { 
  doc,
  collection,
  collectionGroup,
  setDoc,
  updateDoc,
  deleteDoc,
  docSnapshots,
  docData,
  getDoc
} from '@angular/fire/firestore';
import { User } from '@core/interfaces/user';


import { concatMap, from, Observable, of, switchMap } from 'rxjs';
import { DataService } from './data.service';

@Injectable({
  providedIn: 'root',
})
export class AuthService {

  currentUser$ = authState(this.auth);

  constructor(
    private auth: Auth,
    private dataService: DataService
    ) {}

  signUp(name: string, email: string, password: string): Observable<any> {
    return from(
      createUserWithEmailAndPassword(this.auth, email, password)
    ).pipe(switchMap(({ user }) => updateProfile(user, { displayName: name }).then((data) => { this.dataService.upsert(`users/${user.uid}`, data) }) ));
  }
}
Frank van Puffelen
  • 565,676
  • 79
  • 828
  • 807
  • I don't have any *ngFor loops in my template. I am trying to simply save the user data to Firestore when the user signs up. –  Feb 07 '22 at 19:03
  • Could you use a Cloud Function to achieve that. Function listens for user.oncreate (not the correct syntax). I can throw some code this way if that's a way you choose to go. – Michael Cockinos Feb 08 '22 at 00:05

1 Answers1

0

In case you decide to go with a Cloud Function, the below code I use across several different apps and projects and it works faultlessly every time a user either signs up or is created by an 'Admin' user...

const functions = require('firebase-functions');
const admin = require('firebase-admin');
const FieldValue = require('firebase-admin').firestore.FieldValue;

admin.initializeApp();

const db = admin.firestore();



/**
 * Add user to firestore
 */
const creatProfile = (userRecord) => {
    const uid = userRecord.uid;
    const admin = false;
    const email = userRecord.email;
    const photoURL = userRecord.photoUrl || 'enter shortened url for default image';
    const name = userRecord.displayName || 'New User';
    const spouse = userRecord.spouse || 'TBA';
    const forumUserName = userRecord.forumUserName || 'New Username set by admin';
    const address = userRecord.address || 'TBA';
    const suburb = userRecord.suburb || 'TBA';
    const state = userRecord.state || 'QLD';
    const postCode = userRecord.postCode || '2000';
    const homePhone = userRecord.homePhone || '02 1234 5678';
    const mobilePhone = userRecord.mobilePhone || '0400 123 456';
    const memNum = userRecord.memNum || 123;
    const timestamp = FieldValue.serverTimestamp();
    const memType = userRecord.memType || 'Nominated';
    const memStatus = userRecord.memStatus || `Pending`;
    const isStateCoord = userRecord.isStateCoord || false;
    const stateCoordState = userRecord.stateCoordState || 'QLD';
    //const newUserRef = db.doc(`users/${uid}`)
    
    // Convert any date to timestamp for consistency

    

    return db
        .collection(`users`)
        .doc(userRecord.uid)
        .set({
        uid: uid,
        email: email,
        photoURL: photoURL,
        fullName: name,
        mDOB: timestamp,
        spouse: spouse,
        sDOB: timestamp,
        forumUserName: forumUserName,
        address: address,
        suburb: suburb,
        state: state,
        postCode: postCode,
        homePhone: homePhone,
        mobilePhone: mobilePhone,
        memNum: memNum,
        memType: memType,
        memStatus: memStatus,
        memDueDate: timestamp,
        lastLoginDate: timestamp,
        joined: timestamp,
        updated: timestamp,
        admin: admin,
        isAdmin: isAdmin,
        isStateCoord: isStateCoord,
        stateCoordState: stateCoordState,
        
    })
    
        .catch(console.error);
};


exports.authOnCreate = functions.auth.user().onCreate(creatProfile);

All of the info in const createProfile are variables and can be what ever you need them to be.

Michael Cockinos
  • 165
  • 2
  • 15
  • Thank you Michael, I have not thought about using cloud functions but reading up about as I write this. In your experience, what are the advantages in terms of performance, cost and scalability of using cloud functions rather than doing it on the client side? –  Feb 08 '22 at 17:36
  • Hi Michael, getting into cloud functions which I think will be very useful for my project. Nevertheless, trying to deploy your code I ran into the following issue: https://stackoverflow.com/questions/71041057/firebase-functions-not-deploying-but-no-error-am-i-overlooking-something-simple Any chance you could take a look at it to see what you think might be the issue? –  Feb 08 '22 at 21:23
  • Not sure if it makes a difference but you need to have your project on the Blaze plan. Cost wise... Minimal. I have multiple projects using Cloud Functions and it costs me about AU$0.20/month (at the moment). A couple of projects have over 1000 users each. – Michael Cockinos Feb 08 '22 at 22:21
  • Also noticed on the other question, you're using Typescript. Again no problem. I decided to go with Javascript – Michael Cockinos Feb 08 '22 at 22:22
  • Thanks Michael, I am now on the Blaze plan and have created my first user record in fire storage. Again, thank you for the tip. I just have two questions. 1) I noticed that the functions took relatively long time to execute. Are you experiencing the same nd how to handle this in a sign-up flow when you want the user to immediately see their profile page? 2) How do one use the userRecord? Right now the user in my storage gets all the default values (after logical OR). Because I don't think e.g. Spouse is part of the firebase auth object, right? –  Feb 08 '22 at 22:38
  • The userRecord just combines all of what you need to be created when a user signs up or an admin user creates a user. You can code your front end to pass everything through to the function, but I find easier to fill the data after. If you use async/await on the function, the user won't see any difference in performance by the time they get to their data screen and will be able to login without experiencing any delays. – Michael Cockinos Feb 09 '22 at 07:02
  • I use this function in a flutter app during sign up where the user has to answer several screens before getting to the main app. The experience isn't affected and by the time the user gets to the main screen , everything is ready to go. – Michael Cockinos Feb 09 '22 at 07:05
  • Thank you, Michael, for the helpful comments. I can really see many of the benefits of using functions now that I am reading up on it. I also have a several screens. First screen contains just the username, email and password. Then another screen with additional information. How do you manage this in your flutter app, do you create the user after the first screen? If so, how do you update the user record in firestore once they enter more details in the subsequent screen, do you use a different firebase cloud function for that? If so, any chance you could share that piece of code as well? –  Feb 09 '22 at 07:56
  • All of that info is a bit long winded for this question. Happy to talk to you in another forum. Sharing data between screens is relatively easy in Flutter by using MaterialPageRoute (after you have the data from Firestore.)on the sending screen and final variables on the receiving screen. – Michael Cockinos Feb 09 '22 at 10:38
  • Today I wrote a small Medium story on sharing data between screens on flutter: https://medium.com/@mnc12004/flutter-with-firestore-sharing-data-between-screens-5b676dcd8d4e – Michael Cockinos Feb 10 '22 at 10:31
  • Thanks Michael, that seemed super easy. I have to learn Flutter as soon as I am done with learning Angular. –  Feb 10 '22 at 20:10