Collection
Collection Reference
use the collection reference
We use the collection reference to:
-
fetch all documents (it acts as a query):
getDocs(colRef) -
build a query targeting the collection:
query(colRef, filters..) -
build a document reference (random-ID):
doc(colRef), or one that refers to a specific document:doc(colRef, docId) -
add a document to the collection, (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 the simplest path, such as "users" (no starting slash). Sub-collection paths are built from several components.
We set 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 are of any shape and may differ from one another.
The instantiated documents are typed as DocumentData, which is a loose type that doesn't provide information about the content.
We provide a more precise type 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 shapes on the client:
CollectionReference<AppModelType, DbModelType>
DbModel is the representation of the received data, aka the object that the SDK instantiates as a direct translation of the received data, with no transformation. It is DocumentData by default.
We can add a converter to transform it into a different shape for use in the app.
AppModel represents the object as it is after the converter's transformation. It also defaults to DocumentData. We set it to whatever type the converter converts to.
Before sending to Firestore, the converter transforms back AppModel to DbModel.
Transformation examples:
- We transform the DbModel's Timestamp field to an AppModel Date field.
- We add properties to AppModel.
implement the converter
We transform the documents at the app boundaries:
- upon receiving from Firestore (
fromFirestore()) - upon sending to Firestore (
toFirestore())
We define the 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)