问题
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