Skip to content
This repository was archived by the owner on Apr 4, 2023. It is now read-only.

Commit a3a2cd0

Browse files
Manipulating Array fields in Firestore documents via FieldValue #922
1 parent aca5a89 commit a3a2cd0

File tree

14 files changed

+3361
-12904
lines changed

14 files changed

+3361
-12904
lines changed

demo-ng/app/App_Resources/iOS/build.xcconfig

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -4,3 +4,8 @@
44
ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon;
55
ASSETCATALOG_COMPILER_LAUNCHIMAGE_NAME = LaunchImage;
66
CODE_SIGN_ENTITLEMENTS = demong/demong.entitlements
7+
8+
IPHONEOS_DEPLOYMENT_TARGET = 10.0
9+
10+
// Don't check this in because of CI
11+
// DEVELOPMENT_TEAM = 8Q5F6M3TNS

demo-ng/app/tabs/firestore/firestore.component.html

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -10,6 +10,8 @@
1010
<Button text="Update" (tap)="firestoreUpdate()" class="button"></Button>
1111
<Button text="Get" (tap)="firestoreGet()" class="button"></Button>
1212
<Button text="Get nested" (tap)="firestoreGetNested()" class="button"></Button>
13+
<Button text="arrayUnion" (tap)="arrayUnion()" class="button"></Button>
14+
<Button text="arrayRemove" (tap)="arrayRemove()" class="button"></Button>
1315
<Button text="Document Observable (SF)" (tap)="firestoreDocumentObservable()" class="button"></Button>
1416
<Label [text]="(myCity$ | async).name" height="32" *ngIf="myCity$"></Label>
1517
<Button text="Collection Observable (cities)" (tap)="firestoreCollectionObservable()" class="button"></Button>

demo-ng/app/tabs/firestore/firestore.component.ts

Lines changed: 61 additions & 39 deletions
Original file line numberDiff line numberDiff line change
@@ -16,8 +16,8 @@ export class FirestoreComponent {
1616

1717
private listenerUnsubscribe: () => void;
1818

19-
public myCity$: Observable<City>;
20-
public myCities$: Observable<Array<City>>;
19+
myCity$: Observable<City>;
20+
myCities$: Observable<Array<City>>;
2121

2222
private city: City;
2323
private cities: Array<City> = [];
@@ -26,7 +26,7 @@ export class FirestoreComponent {
2626
// AngularFireModule.initializeApp({});
2727
}
2828

29-
public issue854(): void {
29+
issue854(): void {
3030
const helloRef: firestore.DocumentReference =
3131
firebase.firestore()
3232
.collection("users")
@@ -37,7 +37,7 @@ export class FirestoreComponent {
3737
helloRef.get().then(snapshot => console.log(snapshot.data()))
3838
}
3939

40-
public loginAnonymously(): void {
40+
loginAnonymously(): void {
4141
firebase.auth().signInAnonymously()
4242
.then(() => {
4343
const user = firebase.auth().currentUser;
@@ -46,15 +46,15 @@ export class FirestoreComponent {
4646
.catch(err => console.log("Login error: " + JSON.stringify(err)));
4747
}
4848

49-
public firestoreAdd(): void {
49+
firestoreAdd(): void {
5050
firebase.firestore().collection("dogs").add({name: "Fido"})
5151
.then((docRef: firestore.DocumentReference) => {
5252
console.log("Fido added, ref: " + docRef.id);
5353
})
5454
.catch(err => console.log("Adding Fido failed, error: " + err));
5555
}
5656

57-
public firestoreSet(): void {
57+
firestoreSet(): void {
5858
firebase.firestore().collection("dogs").doc("fave")
5959
.set({
6060
name: "Woofie",
@@ -125,14 +125,14 @@ export class FirestoreComponent {
125125
});
126126
}
127127

128-
public firestoreSetByAutoID(): void {
128+
firestoreSetByAutoID(): void {
129129
firebase.firestore().collection("dogs").doc()
130130
.set({name: "Woofie", last: "lastofwoofie", date: new Date()})
131131
.then(() => console.log("Woofie set"))
132132
.catch(err => console.log("Setting Woofie failed, error: " + err));
133133
}
134134

135-
public firestoreUpdate(): void {
135+
firestoreUpdate(): void {
136136
// get a document reference so we can add a city reference to our favourite dog
137137
const sfDocRef: firestore.DocumentReference = firebase.firestore().collection("cities").doc("SF");
138138

@@ -149,7 +149,7 @@ export class FirestoreComponent {
149149
.catch(err => console.log("Updating Woofie failed, error: " + JSON.stringify(err)));
150150
}
151151

152-
public firestoreGet(): void {
152+
firestoreGet(): void {
153153
const collectionRef: firestore.CollectionReference = firebase.firestore().collection("dogs");
154154
collectionRef.get()
155155
.then((querySnapshot: firestore.QuerySnapshot) => {
@@ -174,7 +174,7 @@ export class FirestoreComponent {
174174
});
175175
}
176176

177-
public firestoreGetNested(): void {
177+
firestoreGetNested(): void {
178178
const mainStreetInSFDocRef: firestore.DocumentReference =
179179
firebase.firestore()
180180
.collection("cities")
@@ -194,6 +194,28 @@ export class FirestoreComponent {
194194
});
195195
}
196196

197+
arrayUnion(): void {
198+
firebase.firestore().collection("dogs").doc("fave")
199+
.update({
200+
last: "Updated From arrayUnion",
201+
updateTs: firebase.firestore().FieldValue().serverTimestamp(),
202+
colors: firebase.firestore().FieldValue().arrayUnion(["red", "blue"])
203+
})
204+
.then(() => console.log("Woofie updated from arrayUnion"))
205+
.catch(err => console.log("Updating Woofie from arrayUnion failed, error: " + JSON.stringify(err)));
206+
}
207+
208+
arrayRemove(): void {
209+
firebase.firestore().collection("dogs").doc("fave")
210+
.update({
211+
last: "Updated From arrayRemove",
212+
updateTs: firebase.firestore().FieldValue().serverTimestamp(),
213+
colors: firebase.firestore().FieldValue().arrayRemove(["red"])
214+
})
215+
.then(() => console.log("Woofie updated from arrayRemove"))
216+
.catch(err => console.log("Updating Woofie from arrayRemove failed, error: " + JSON.stringify(err)));
217+
}
218+
197219
firestoreDocumentObservable(): void {
198220
this.myCity$ = Observable.create(subscriber => {
199221
const docRef: firestore.DocumentReference = firebase.firestore().collection("cities").doc("SF");
@@ -219,7 +241,7 @@ export class FirestoreComponent {
219241
});
220242
}
221243

222-
public firestoreListen(): void {
244+
firestoreListen(): void {
223245
if (this.listenerUnsubscribe !== undefined) {
224246
console.log("Already listening ;)");
225247
return;
@@ -236,7 +258,7 @@ export class FirestoreComponent {
236258
});
237259
}
238260

239-
public firestoreStopListening(): void {
261+
firestoreStopListening(): void {
240262
if (this.listenerUnsubscribe === undefined) {
241263
console.log("Please start listening first ;)");
242264
return;
@@ -246,7 +268,7 @@ export class FirestoreComponent {
246268
this.listenerUnsubscribe = undefined;
247269
}
248270

249-
public firestoreWhere(): void {
271+
firestoreWhere(): void {
250272
const cityDocRef = firebase.firestore().collection("cities").doc("SF");
251273

252274
firebase.firestore().collection("dogs")
@@ -260,7 +282,7 @@ export class FirestoreComponent {
260282
.catch(err => console.log("Where-get failed, error: " + err));
261283
}
262284

263-
public firestoreWhereOrderLimit(): void {
285+
firestoreWhereOrderLimit(): void {
264286
const query: firestore.Query = firebase.firestore().collection("cities")
265287
.where("state", "==", "CA")
266288
.where("population", "<", 99999999)
@@ -277,7 +299,7 @@ export class FirestoreComponent {
277299
.catch(err => console.log("firestoreWhereOrderLimit failed, error: " + err));
278300
}
279301

280-
public firestoreWhereCityHasALake(): void {
302+
firestoreWhereCityHasALake(): void {
281303
const query: firestore.Query = firebase.firestore().collection("cities")
282304
.where("landmarks", "array-contains", "lake");
283305

@@ -291,7 +313,7 @@ export class FirestoreComponent {
291313
.catch(err => console.log("firestoreWhereCityHasALake failed, error: " + err));
292314
}
293315

294-
public firestoreDelete(): void {
316+
firestoreDelete(): void {
295317
firebase.firestore().collection("dogs").doc("fave")
296318
.delete()
297319
.then(() => {
@@ -300,15 +322,15 @@ export class FirestoreComponent {
300322
.catch(err => console.log("Delete failed, error: " + err));
301323
}
302324

303-
public doWebGetValueForCompanies(): void {
325+
doWebGetValueForCompanies(): void {
304326
const path = "/companies";
305327
firebase.database().ref(path)
306328
.once("value")
307329
.then(result => console.log(`${result.key} => ${JSON.stringify(result.val())}`))
308330
.catch(error => console.log("doWebGetValueForCompanies error: " + error));
309331
}
310332

311-
public writeBatch(): void {
333+
writeBatch(): void {
312334
// one batch can update multiple docs
313335
const sfDocRef: firestore.DocumentReference = firebase.firestore().collection("cities").doc("SF");
314336
const sacDocRef: firestore.DocumentReference = firebase.firestore().collection("cities").doc("SAC");
@@ -323,7 +345,7 @@ export class FirestoreComponent {
323345
.catch(error => console.log("Batch error: " + error));
324346
}
325347

326-
public transactionalUpdate(): void {
348+
transactionalUpdate(): void {
327349
const sfDocRef: firestore.DocumentReference = firebase.firestore().collection("cities").doc("SF");
328350

329351
firebase.firestore().runTransaction(transaction => {
@@ -345,29 +367,29 @@ export class FirestoreComponent {
345367
.catch(error => console.log("doTransaction error: " + error));
346368
}
347369

348-
public firestoreStartAt(): void {
370+
firestoreStartAt(): void {
349371
firebase.firestore().collection('cities')
350-
.doc('LA')
351-
.get()
352-
.then(doc => {
353-
firebase.firestore().collection('cities')
354-
.orderBy('name', 'asc')
355-
.startAt(doc)
356-
.get()
357-
.then(snap => snap.forEach(doc => console.log(doc.id, doc.data())));
358-
});
372+
.doc('LA')
373+
.get()
374+
.then(doc => {
375+
firebase.firestore().collection('cities')
376+
.orderBy('name', 'asc')
377+
.startAt(doc)
378+
.get()
379+
.then(snap => snap.forEach(doc => console.log(doc.id, doc.data())));
380+
});
359381
}
360382

361-
public firestoreStartAfter(): void {
383+
firestoreStartAfter(): void {
362384
firebase.firestore().collection('cities')
363-
.doc('LA')
364-
.get()
365-
.then(doc => {
366-
firebase.firestore().collection('cities')
367-
.orderBy('name', 'asc')
368-
.startAfter(doc)
369-
.get()
370-
.then(snap => snap.forEach(doc => console.log(doc.id, doc.data())));
371-
});
385+
.doc('LA')
386+
.get()
387+
.then(doc => {
388+
firebase.firestore().collection('cities')
389+
.orderBy('name', 'asc')
390+
.startAfter(doc)
391+
.get()
392+
.then(snap => snap.forEach(doc => console.log(doc.id, doc.data())));
393+
});
372394
}
373395
}

demo-ng/package.json

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -24,12 +24,12 @@
2424
"@angular/router": "~6.1.0",
2525
"nativescript-angular": "^6.1.0",
2626
"nativescript-camera": "^4.0.2",
27-
"nativescript-imagepicker": "^6.0.1",
27+
"nativescript-imagepicker": "~6.0.4",
2828
"nativescript-plugin-firebase": "file:../publish/package/nativescript-plugin-firebase-7.0.1.tgz",
2929
"nativescript-theme-core": "~1.0.4",
3030
"reflect-metadata": "~0.1.10",
3131
"rxjs": "~6.0.0 || >=6.1.0",
32-
"tns-core-modules": "~4.2.0",
32+
"tns-core-modules": "~4.2.1",
3333
"zone.js": "~0.8.26"
3434
},
3535
"devDependencies": {

demo-ng/tsconfig.json

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -26,4 +26,4 @@
2626
"node_modules",
2727
"platforms"
2828
]
29-
}
29+
}

docs/FIRESTORE.md

Lines changed: 17 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -218,6 +218,23 @@ query
218218
});
219219
```
220220

221+
### Adding to (`arrayUnion`) and removing from (`arrayRemove`) Arrays
222+
If you don't want to set the entire contents of an Array, see [the official docs (at the bottom: "Update elements in an array")](https://firebase.google.com/docs/firestore/manage-data/add-data) and use this:
223+
224+
```typescript
225+
firebase.firestore().collection("dogs").doc("fave")
226+
.update({
227+
colors: firebase.firestore().FieldValue().arrayUnion(["red", "blue"])
228+
});
229+
```
230+
231+
```typescript
232+
firebase.firestore().collection("dogs").doc("fave")
233+
.update({
234+
colors: firebase.firestore().FieldValue().arrayRemove(["red"])
235+
});
236+
```
237+
221238
### Ordering and limiting results of `collection.where()`
222239
Return data sorted (asc or desc), or limit to a certain number of results:
223240

src/app/firestore/index.ts

Lines changed: 6 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -7,8 +7,12 @@ export module firestore {
77
}
88

99
FieldValue(): firebase.firestore.FieldValue {
10-
return {
11-
serverTimestamp: () => "SERVER_TIMESTAMP"
10+
return <any>{
11+
type: undefined,
12+
value: undefined,
13+
serverTimestamp: () => "SERVER_TIMESTAMP",
14+
arrayUnion: (fields: Array<any>) => new firebase.firestore.FieldValue("ARRAY_UNION", fields),
15+
arrayRemove: (fields: Array<any>) => new firebase.firestore.FieldValue("ARRAY_REMOVE", fields)
1216
}
1317
}
1418

src/firebase-common.ts

Lines changed: 7 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -7,7 +7,12 @@ import * as mlkit from "./mlkit";
77

88
// note that this implementation is overridden for iOS
99
export class FieldValue {
10+
constructor(public type: firestore.FieldValueType, public value: any) {
11+
};
12+
1013
serverTimestamp = () => "SERVER_TIMESTAMP";
14+
arrayUnion = (fields: Array<any>) => new FieldValue("ARRAY_UNION", fields);
15+
arrayRemove = (fields: Array<any>) => new FieldValue("ARRAY_REMOVE", fields);
1116
}
1217

1318
export class GeoPoint {
@@ -26,9 +31,7 @@ export const firebase: any = {
2631
storage,
2732
mlkit,
2833
firestore: {
29-
FieldValue: {
30-
serverTimestamp: () => "SERVER_TIMESTAMP"
31-
},
34+
FieldValue,
3235
GeoPoint: (latitude: number, longitude: number) => new GeoPoint(latitude, longitude)
3336
},
3437
invites: {
@@ -217,6 +220,7 @@ export const firebase: any = {
217220

218221
export abstract class DocumentSnapshot implements firestore.DocumentSnapshot {
219222
public data: () => firestore.DocumentData;
223+
220224
constructor(public id: string, public exists: boolean, documentData: firestore.DocumentData) {
221225
this.data = () => exists ? documentData : undefined;
222226
}

src/firebase.android.ts

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,7 @@
11
import {
22
DocumentSnapshot as DocumentSnapshotBase,
33
firebase,
4+
FieldValue,
45
GeoPoint,
56
QuerySnapshot,
67
isDocumentReference
@@ -117,6 +118,15 @@ firebase.toHashMap = obj => {
117118
// note that the Android Firestore SDK only supports this for 'update' (not for 'set')
118119
if (obj[property] === "SERVER_TIMESTAMP") {
119120
node.put(property, com.google.firebase.firestore.FieldValue.serverTimestamp());
121+
} else if (obj[property] instanceof FieldValue) {
122+
const fieldValue: FieldValue = obj[property];
123+
if (fieldValue.type === "ARRAY_UNION") {
124+
node.put(property, com.google.firebase.firestore.FieldValue.arrayUnion(fieldValue.value));
125+
} else if (fieldValue.type === "ARRAY_REMOVE") {
126+
node.put(property, com.google.firebase.firestore.FieldValue.arrayRemove(fieldValue.value));
127+
} else {
128+
console.log("You found a bug! Please report an issue at https://github.com/EddyVerbruggen/nativescript-plugin-firebase/issues, mention fieldValue.type = '" + fieldValue.type + "'. Thanks!")
129+
}
120130
} else if (obj[property] instanceof Date) {
121131
node.put(property, new java.util.Date(obj[property].getTime()));
122132
} else if (obj[property] instanceof GeoPoint) {

src/firebase.d.ts

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -828,8 +828,14 @@ export namespace firestore {
828828
commit(): Promise<void>;
829829
}
830830

831+
export type FieldValueType = "ARRAY_UNION" | "ARRAY_REMOVE";
832+
831833
export class FieldValue {
834+
constructor(type: FieldValueType, value: any);
835+
832836
static serverTimestamp: () => "SERVER_TIMESTAMP";
837+
static arrayUnion: (fields: Array<any>) => FieldValue;
838+
static arrayRemove: (fields: Array<any>) => FieldValue;
833839
}
834840

835841
export interface QuerySnapshot {

0 commit comments

Comments
 (0)