Update a map field in all documents in Firestore

主宰稳场 提交于 2021-01-05 05:42:45

问题


I'm doing a web application in Angular 6 and the library angularfire2. I have two collections, teams and users. If a team's name is changed, the team's name must also be changed inside of the user's document.

export interface User {
  firstName: string;
  emailAddress: string;
  team: TeamId;
}

export interface UserId extends User {
  id: string;
}

export interface Team {
  name: string;
  abbreviation?: string;
}

export interface TeamId extends Team {
  id: string;
}

Class:

const team: Team = {
  name: 'Yankees',
  abbreviation: 'YKS'
};

this.teamService.updateTeam('kbdf673bksbdj', team)
  .then(async res => {
    // The name has changed
    if ('Yankees' !== team.name) {
      await this.teamService.updateTeamNameInOthers('kbdf673bksbdj', team.name);
    }
  }, err => {
  });

From the service:

private teamsCollection: AngularFirestoreCollection<Team>;
teams: Observable<TeamId[]>;

constructor(
  private afs: AngularFirestore) {
  this.teamsCollection = afs.collection<Team>('teams');

  this.teams = this.teamsCollection.snapshotChanges().pipe(
    map(actions => actions.map(a => {
      const data = a.payload.doc.data() as Team;
      const id = a.payload.doc.id;
      return {id, ...data};
    }))
  );
}

updateTeamNameInOthers(id: string, newName: string) {
  this.userService.getUsers().subscribe(users => {
    users.forEach(user => {
      if (user.team.id === id) {
        this.afs.collection('users').doc(user.id)
          .set({team: {name: newName}}, {merge: true});

        // I have tried
        // .update({team: {name: newName}});
      }
    });
  });
}

I have tried (transaction):

updateTeamNameInOthers(id: string, newName: string) {
    this.userService.getUsers().subscribe(users => {
      users.forEach(user => {
        if (user.team.id === id) {
          const userRef = this.afs.collection(config.collection_users).doc(user.id).ref;

          this.afs.firestore.runTransaction(transaction => {
            transaction.get(userRef).then(userDoc => {
              transaction.update(userRef, {team: {name: newname}}, {merge: true});
            });
          });
        }
      });
    });
  }

You can update any property of Team right now but, if you want to change all the properties of the team at the same time, the user's document is not updated. I'm not sure if this is the best way to do it anyway.

My goal is, if the team's name changed, change the team's name in all user's document who belongs to that team.


回答1:


there are a lot of ways you can do this. I don't recommend making these changes from the front end.

If your database structure is:

teams/team

users/user {
  team: { (not as a subcollection)
    id: string;
  }
}

Then you'll have to query all of the users like you are. The difference is that you'll want to use a transaction to perform the operations all together or not at all if there is an error. Then you'll use the transaction to do the DB operations like this snipped from the linked answer db.firestore.runTransaction(transaction => and transaction.X where X is your DB method.

Alternatively, I recommend doing this kind of functionality with Cloud Functions. With this, you can listen to changes on the record without having to rely on the client side to make the changes. If the user makes a valid change, the Cloud Functions can do the transactional changes instead of performing the changes unreliably on the client side.

After edit 1 You should move the this.afs.firestore.runTransaction(transaction => { line above the users.forEach(user => { so all of the operations can share the same transaction. This way if there is an error with one of the updates, none of them will update.

After chat The final solution was to use async/await with firestore.runTransaction, return a promise, update the records with transaction.update, resolving the promise after. A second part of the solution was to ensure you are not subscribed to the collection you are updating within the update!

// service method
getData(value: string): Promise<FancyType[]> {
  return this.afs.collection<FancyType>(
    'collection',
    ref => ref.where('value', '==', value)
  ).snapshotChanges()
  .pipe(
    take(1),
  ).toPromise();
}

// update method 1 transaction
const data: FancyType[] = await this.service.getData();

if (data) {
  await this.afs.firestore.runTransaction(t => {
    return new Promise((res, rej) => {
      data.forEach(d => {
        const dataRef = this.afs.collection('collection').doc(d.id).ref;
        transaction.update(dataRef, {...d, hello: 'World'});
      });
      res();
    });
  });
}

// alternate update method 2 batch
const data: FancyType[] = await this.service.getData();
const batch = this.afs.firestore.batch();

if (data) {
  data.forEach(d => {
    const dataRef = this.afs.collection('collection').doc(d.id).ref;
    batch.update(dataRef, {...d, hello: 'World'});
  });

  batch.commit()
    .then(res => {
      //on success
    }).catch(err => {
      //on error
    });
}

Here is a reference for transactions and batches.



来源:https://stackoverflow.com/questions/54814504/update-a-map-field-in-all-documents-in-firestore

易学教程内所有资源均来自网络或用户发布的内容,如有违反法律规定的内容欢迎反馈
该文章没有解决你所遇到的问题?点击提问,说说你的问题,让更多的人一起探讨吧!