Reputation: 4405
I code the code bellow that is notified in real time manner. I mean it keeps observing and soon any collection fields are updated it is loaded in Angular page.
app.component.ts
import { Component } from '@angular/core';
import { Observable } from 'rxjs';
import { AngularFirestore } from '@angular/fire/firestore';
@Component({
selector: 'app-root',
templateUrl: './app.component.html'
})
export class AppComponent {
public transfers: Observable<any[]>;
constructor(db: AngularFirestore) {
this.transfers = db.collection('/transfer').valueChanges();
}
}
app.component.html
<ul>
<li *ngFor="let transfer of transfers | async">
<pre>{{ transfer | json }}</pre>
</li>
</ul>
My problem is that I don't know how to add an idToken while observing with either db.collection().valueChanges() or snapshotChanges(). Well, I know how generate the idToken outside the application from a Custom Token and for this question I want to focus only how to add such idToken while "listening" valueChanges() or snapshotChanges() of a specific document. Imagine each document is a diferent transaction interested only to a single user.
The above code works as expected if I change the Firestore rules to "allow read to all" but I want to find someway to only allow Angular to read if it pass the idToken and, on top of that, observe single document changes instead of observe all documents from an entire collection as code above does.
Here are some tentatives failling just to exemplify what I am trying. I don't know how to add idToken in any of them. I added also an example what I am imagining could work without AngularFire. I guess I am confused about three points here:
some very basic idea how to observe/listen a single document
how add a idToken similar as I do with curl/postman
it isn't possible to listen/observe a single document. I must observe entire collection and filter.
Tentative 1:
this.uniqueTransfer = db.collection('/transfer',
ref => ref.where("id", "==", "1"))
.snapshotChanges().pipe(map(actions => actions.map(a => a.payload.doc.data()))
);;
Tentative 2:
this.uniqueTransfer = db.collection('/transfer', ref => ref.where("id", "==", "1")).snapshotChanges().pipe(
map(actions => actions.map(a => {
const data = a.payload.doc.data();
return { data };
}))
Tentative 3:
db.doc(`transfer/Wsj0dysyHHok3xupwDhD`) //this is the id copied from Firebase console
.snapshotChanges()
.pipe()
.subscribe();
Tentative 4 without AngularFire
constructor(private http: HttpClient) {
this.getTranfers();
}
public getTranfers() {
const headers = { 'Authorization': 'valid idtoken working with curl' }
const body = JSON.stringify({
"structuredQuery": {
"where": {
"fieldFilter": {
"field": { "fieldPath": "id" },
"op": "EQUAL",
"value": { "stringValue": "4" }
}
},
"from": [{ "collectionId": "transfer" }]
}
})
this.http.post<any>('https://firestore.googleapis.com/v1/projects/firetestjimis/databases/(default)/documents:runQuery', body, { headers }).subscribe(data => {
this.uniqueTransfer = data;
})
}
And the expected behaviour is listening for a especific document be changed and update front end like:
<div>{{(uniqueTransfer|async)?.status}}</div>
Finally, in case it adds here, I can query a single document with this curl. Obvisouly it is not listening/observing the document. It just retrieves it.
curl --location --request POST 'https://firestore.googleapis.com/v1/projects/firetestjimis/databases/(default)/documents:runQuery' \
--header 'Authorization: Bearer certain idToken resulted from a Custom Token' \
--header 'Content-Type: application/json' \
--data-raw '{
"structuredQuery": {
"where" : {
"fieldFilter" : {
"field": {"fieldPath": "id"},
"op":"EQUAL",
"value": {"stringValue": "1"}
}
},
"from": [{"collectionId": "transfer"}]
}
}'
*** edited After gso_Gabriel's two suggestions.
FIRST SUGGESTION)
I tried follow https://github.com/angular/angularfire/issues/2109. Well it seems to be a suggestion for new feature than really a current alternative. BTW, I gave a try with:
this.transfers = db.doc<any>(`transfer/sDme6IRIi4ezfeyfrU7y`).valueChanges();
sDme6IRIi4ezfeyfrU7y stands for a specific document but I got all documents from transfer collection (same behaviour as this.transfers = db.collection('/transfer').valueChanges();)
SECOND SUGGESTION)
import { map } from 'rxjs/operators';
import 'rxjs/Rx';
@Component({
selector: 'app-root',
templateUrl: './app.component.html'
})
export class AppComponent {
public transfers: Observable<any[]>;
uniqueTransfer: any;
transferCollectionRef: AngularFirestoreCollection<any>;
constructor(db: AngularFirestore) {
this.transferCollectionRef = db.collection<any>('transfer', ref => ref.where("id", "==", "1"));
this.transfers = this.transferCollectionRef.snapshotChanges().map(actions => {
return actions.map(action => {
const data = action.payload.doc.data();// as Todo;
const id = action.payload.doc.id;
return { id, ...data };
});
});
}
This worked. I can see the filter "where" is applied (... ref => ref.where("id", "==", "1")
Now I am missing how to use the idToken resulted from Custom Tokem. I guess it must exist someway like we do with HttpClient (see my Tentative 4 above to understand what is common approach with header).
*** In case it add somehow here, even the similar question posted on github didn't get any comment other than someone saying is looking for same answer https://github.com/angular/angularfire/issues/2419
*** FINAL SOLUTION THANKS TO gstvg
export class AppComponent {
public transfers: Observable<any[]>;
transferCollectionRef: AngularFirestoreCollection<any>;
constructor(public auth: AngularFireAuth, public db: AngularFirestore) {
this.listenSingleTransferWithToken();
}
async listenAllTransfersWithToken() {
await this.auth.signInWithCustomToken("eyJh...w8l-NO-rw");
this.transfers = this.db.collection('/transfer').valueChanges();
}
async listenSingleTransferWithToken() {
await this.auth.signInWithCustomToken("eyJ...w8l-NO-rw");
this.transferCollectionRef = this.db.collection<any>('transfer', ref => ref.where("id", "==", "1"));
this.transfers = this.transferCollectionRef.snapshotChanges().map(actions => {
return actions.map(action => {
const data = action.payload.doc.data();
const id = action.payload.doc.id;
return { id, ...data };
});
});
}
}
Upvotes: 1
Views: 1354
Reputation: 927
The firestore client auth is handled by firebase auth, so, to make authenticated calls, you must authenticate with firebase auth on the frontend, with your custom token generated on the backend, using firebase.auth.signinWithCustomToken(customToken):
import { Component } from '@angular/core';
import { Observable } from 'rxjs';
import { AngularFireAuth } from '@angular/fire/auth';
import { auth } from 'firebase/app';
import { AngularFirestore } from '@angular/fire/firestore';
@Component({
selector: 'app-root',
templateUrl: './app.component.html'
})
export class AppComponent {
public transfers: Observable<any[]>;
constructor(db: AngularFirestore, public auth: AngularFireAuth) {
await auth.signinWithCustomToken(customToken);
this.transfers = db.collection('/transfer').valueChanges();
}
}
https://firebase.google.com/docs/reference/js/firebase.auth.Auth#signinwithcustomtoken
https://github.com/angular/angularfire/blob/master/docs/auth/getting-started.md
Upvotes: 1