Cloud Firestore is a NoSQL, document-oriented database. Unlike a SQL database, there are no tables or rows. Instead, you store data in documents, which are organized into collections. Each document contains a set of key-value pairs. Cloud Firestore is optimized for storing large collections of small documents.
The AngularFirestoreDocument
service is a wrapper around the native Firestore SDK's DocumentReference
type. It is a generic service that provides you with a strongly typed set of methods for manipulating and streaming data. This service is designed for use as an @Injectable()
.
import { Component } from '@angular/core';
import { AngularFirestore, AngularFirestoreDocument } from '@angular/fire/firestore';
import { Observable } from 'rxjs';
export interface Item { name: string; }
@Component({
selector: 'app-root',
template: `
<div>
{{ (item | async)?.name }}
</div>
`
})
export class AppComponent {
private itemDoc: AngularFirestoreDocument<Item>;
item: Observable<Item>;
constructor(private afs: AngularFirestore) {
this.itemDoc = afs.doc<Item>('items/1');
this.item = this.itemDoc.valueChanges();
}
update(item: Item) {
this.itemDoc.update(item);
}
}
With the exception of the valueChanges()
, each streaming method returns an Observable of DocumentChangeAction[]
.
A DocumentChangeAction
gives you the type
and payload
properties. The type
tells when what DocumentChangeType
operation occured (added
, modified
, removed
). The payload
property is a DocumentChange
which provides you important metadata about the change and a doc
property which is the DocumentSnapshot
.
interface DocumentChangeAction {
//'added' | 'modified' | 'removed';
type: DocumentChangeType;
payload: DocumentChange;
}
interface DocumentChange {
type: DocumentChangeType;
doc: DocumentSnapshot;
oldIndex: number;
newIndex: number;
}
interface DocumentSnapshot {
exists: boolean;
ref: DocumentReference;
id: string;
metadata: SnapshotMetadata;
data(): DocumentData;
get(fieldPath: string): any;
}
There are multiple ways of streaming collection data from Firestore.
What is it? - Returns an Observable of document data. All Snapshot metadata is stripped. This method provides only the data.
Why would you use it? - When you just need the object data. No document metadata is attached which makes it simple to render to a view.
When would you not use it? - When you need the id
of the document to use data manipulation methods. This method assumes you either are saving the id
to the document data or using a "readonly" approach.
What is it? - Returns an Observable of data as a DocumentChangeAction
.
Why would you use it? - When you need the document data but also want to keep around metadata. This metadata provides you the underyling DocumentReference
and document id. Having the document's id around makes it easier to use data manipulation methods. This method gives you more horsepower with other Angular integrations such as ngrx, forms, and animations due to the type
property. The type
property on each DocumentChangeAction
is useful for ngrx reducers, form states, and animation states.
When would you not use it? - When you simply need to render data to a view and don't want to do any extra processing.
AngularFirestore provides methods for setting, updating, and deleting document data.
set(data: T)
- Destructively updates a document's data.update(data: T)
- Non-destructively updates a document's data.delete()
- Deletes an entire document. Does not delete any nested collections.
Querying has no effect on documents. Documents are a single object and querying effects a range of multiple documents. If you are looking for querying then you want to use a collection.
Nesting collections is a great way to structure your data. This allows you to group related data structures together. If you are creating a "Task List" site, you can group "tasks" under a user: user/<uid>/tasks
.
To retrieve a nested collection use the collection(path: string)
method.
constructor(private afs: AngularFirestore) {
this.userDoc = afs.doc<Item>('user/david');
this.tasks = this.userDoc.collection<Task>('tasks').valueChanges();
}