DEV Community

loading...

Query & Update Firestore Documents in Angular 7

crazedvic profile image Vic Rubba ・3 min read

February 14th, 2019

As I continue to build the time tracking system I run into challenges. Challenges so annoying that I inevitably always end up having to scour stackoverflow for hours on end. Searching desperately for relevant knowledge and eventually a viable solution. And now I am here to share those latest findings.

So I have a week navigation control. Users can move forward or backward through the weeks of the year. I key timesheets on year_week, and user_id. As the user clicks forward or back, it should load the right timesheet, if it exists and populate the form (as discussed in my previous post). Then if the user makes updates to the timesheet, it should update the document in Firestore. Simple enough.

So the key requirements here are: access document via a query, NOT id, show that document's data, have a reference to the underlying document, so I can update it, be fast, and minimize trips to the server, in the interest of my wallet.

Here's my Client interface, for reference:

export interface Client {
    name: string;
    phone: string;
}

Here's the variables I'm going to need for this, declared in AppComponent.ts:

    private clientRef: DocumentReference;
    clients: Observable<any[]>;
    myClient: Client;

Quick explanation:

  • clientRef = will hold a DocumentReference to the selected Client
  • clients = will hold a list of all Clients to select from
  • myClient = will be the currently selected Client

Here's the html form, AppComponent.html:

<ul>
    <li class="text" *ngFor="let client of clients | async">
        <a href="#" (click)="selectClient(client)">{{client.name}}</a>
    </li>
</ul>
<div>Selected Client: {{myClient?.name}}</div>
<button (click)="updateSelectedClient()">Update Client</button>

So basically load up all the clients, click client name to select it. It will show up as the selected client, then click Update Client to see the Client update in both the list, and in the Firestore console, in real time.

When a user clicks a client name, I am actually going to perform a query by client name, as I don't have ID's. This is intentional because of my actual use case, as outlined in the introduction. Here's what happens:

async selectClient(client: Client) {

     const snapshotResult = await this.db.collection('clients', ref =>
        ref.where('name', '==', client.name)
           .limit(1))
           .snapshotChanges()
           .pipe(flatMap(clients => clients)); 

        snapshotResult.subscribe(doc => {
            this.myClient = <Client>doc.payload.doc.data();
            this.clientRef = doc.payload.doc.ref;
        });

You'll notice I pipe the results into flatMap, this flattens the result array which only has 1 result anyway, making the remaining code cleaner. I wait for the snapshotResult to come back, then I grab and assign my DocumentReference to clientRef and populate myClient with the document data.

Finally to test if this will allow me to update, I created the following function:

updateSelectedClient() {
  this.myClient.name = this.myClient.name + '.';
  this.clientRef.update(this.myClient);
}

I am just appending a period to the end of the client name, to make sure this is working. Clicking the UpdateClient button I could see the Selected Client label, the Firestore document and the name in the list update in real time.

ADDENDUM

You can achieve the same result by using BehaviorSubject, so clicking on a client merely changes the value of the subject, which in turn will trigger the query. As my current year-week is already a subject, I figured I'd quickly add this.

private _currentClient: BehaviorSubject<string>;

Then initialize it in the constructor

this._currentClient = new BehaviorSubject('');

Add an ngOnInit and subscribe to the subject

ngOnInit() {
        this._currentClient.subscribe((val: string) => {
            this.snapshotResult = this.db.collection('clients', ref =>
                ref.where('name', '==', val).limit(1)).snapshotChanges()
                .pipe(flatMap(clients => clients));

            this.snapshotResult.subscribe(doc => {
                this.myClient = <Client>doc.payload.doc.data();
                this.clientRef = doc.payload.doc.ref;
            });
        });
    }

And change selectClient() function to just this:

async selectClient(client: Client) {
        this._currentClient.next(client.name);
}

The functionality overall remains the same, just using SubjectBehavior instead.

Discussion (0)

pic
Editor guide