Collection
Collection Reference
collection reference usage
We provide the collection reference to:
-
fetch all documents -
getDocs(colRef) -
build a query targeting the collection -
query(colRef, filters..) -
build a random-ID document reference -
doc(colRef), or one that refers to a specific document -doc(colRef, docId) -
add a document to the collection, with a random ID generated on the fly -
addDoc(colRef, data).
build a collection reference
We use a path to identify the collection (uniquely). Root collections have a simple path, such as "users" (no starting slash). Sub-collection paths are made from several components.
We provide the path as:
-
a single string, with slash separators.
-
a sequence of strings, with no slash separators.
const colRef = collection(db, "users")
const colRef = collection(db, `users/${uid}/custom_list`)
const colRef = collection(db, "users", uid, "custom_list")
const colRef = db.collection(`users/${uid}/custom_list`) // sane
TypeScript: set the document's type at the collection level.
Collections are schema-less: they don't define the shape of their documents.
When receiving document data from the database, the client SDK checks the actual data and instantiates documents with it. The instantiated documents may be of any shape and may be different from one another.
The instantiated documents are typed as DocumentData, which is a loose type that doesn't provide information about the content.
We should provide a more precise type. We set it at the collection reference level. We do it through a type assertion:
const colRef = collection(db, "players") as CollectionReference<Player, Player>
Instantiated documents are now of type Player.
Converter
The SDK supports having two document types on the client:
CollectionReference<AppModelType, DbModelType>
DbModel, which is DocumentData by default, represents the shape instantiated by the SDK when receiving data.
If we want to transform instantiated documents into a different shape for use within the app, we use a converter.
AppModel, which is also DocumentData by default, is the type parameter that represents the type after conversion. We set it to whatever type the converter converts to.
Before sending to Firestore, the converter should transforms back AppModel to DbModel.
Transformation examples:
- DbModel has a Timestamp field but we want AppModel to have a Date field.
- We add properties to AppModel, that are not present on DbModel.
implement the converter
We transform the documents at the app boundaries:
- upon receiving from Firestore (
fromFirestore()) - upon preparing to send to Firestore (
toFirestore())
We define two functions and add them to the converter.
fromFirestore() takes the snapshot as instantiated:
fromFirestore(snapshot: QueryDocumentSnapshot<FirestoreWorkout>): Workout{
// to client shape
const firestoreWorkout = snapshot.data()
const workout = { ...firestoreItem, date: firestoreItem.date.toDate()}
return workout
}
toFirestore() takes the object in its app-side shape.
toFirestore(workout: Workout) {
// to database shape
return { ...workout, date: Timestamp.fromDate(workout.date)}
}
We gather the transforms in the converter (FirestoreDataConverter). While the type may be inferred from the transforms, we may still add them for safety.
// FirestoreDataConverter<AppModel, DbModel>
const myConverter: FirestoreDataConverter<Workout, FirestoreWorkout> = {
toFirestore() {},
fromFirestore() {},
}
We attach it to the collection reference to let it type its documents.
const colRef = collection(db, "players").withConverter(conv)