0

My code for accessing a document from firestore is as below.

let data;
this.firestore.collection('groups').doc(tempId).ref.get().then(function(doc) {
  if (doc.exists) {
      data = doc.data();
      console.log("Document data:", doc.data());   // Gives correct data here
  } else {
      console.log("No such document!");
  }
}).catch(function(error) {
    console.log("Error getting document:", error);
});
console.log("Service Data :: " + data); //It says undefined here.

Here, I want to return the data of doc.data() to another component. But, In the console.log("Service Data :: " + data); it says undefined.

So, I'm confused like why data variable does not has the value of doc.data() in it.

Virus
  • 303
  • 1
  • 3
  • 10
  • Ok. Sorry. Took back my aswer. I was unable to test my code today, so sorry. Will get back with a tested solution, but it will rely on `valueChanges()` and observables rather than Promises, if that's ok. – thomi Mar 22 '19 at 18:03
  • I'm fine with it as far as code works – Virus Mar 23 '19 at 01:47

2 Answers2

2

This might be the reason , where second console statement is executing before first. Thread based execution will make the request to your firestore and it will not wait for the response where as it will executes other lines of code. So your second console is executing before the first.

let data;
this.firestore.collection('groups').doc(tempId).ref.get().then(function(doc) {
  if (doc.exists) {
      data = doc.data();
      console.log("Document data:", doc.data());   // first console
  } else {
      console.log("No such document!");
  }
}).catch(function(error) {
    console.log("Error getting document:", error);
});
console.log("Service Data :: " + data); //second console

If you want to change this behavior try call your second console next to the first one.

If you want pass the data to other component use BehaviourSubject

  public  dataSource = new BehaviorSubject<any>([]);

  this.dataSource.next(doc.data()); 
  console.log("Document data:", doc.data());   // first console

Try to pass dataSource object to your other component with help of service class. follow this link for more info about angular-behaviorsubject-service

second Component.ts

constructor(private service: Commonservice) { }

someMethod() {
  this.service.dataSource.subscribe((response: any) =>{
    // do something with data
  })
}
Ganesh
  • 5,808
  • 2
  • 21
  • 41
  • oh I see. But What should I do if I want to pass the data to another component? – Virus Mar 22 '19 at 05:47
  • Hi, I'm sorry but I'm new to this so would you please help me with the code how can I achieve that? I tried to understand the link but cannot compare it with my requirements. – Virus Mar 22 '19 at 07:17
  • @Virus i have updated my answer check it once. I hope this will solve your issue. – Ganesh Mar 22 '19 at 09:12
  • I tried but got error like "Error getting document: TypeError: Cannot read property 'dataSource' of undefined". Moreover when I subscribe How I get the data there mean in which variable? – Virus Mar 22 '19 at 17:32
1

the .get() method returns a promise, which is executed asynchronously once you call .then(). Because of this, the next line that gets executed is console.log("Service Data :: " + data);. Javascript does not wait for the promise to be resolved and instead just carries on with the next synchronous line which is the second console.

The way I usually go about this is passing the whole promise to the other component or better yet, I use the .valueChanges() of the .doc() which returns an observable, and use the async pipe in the component I'm passing to:

// Get Observable on document. Returns Observable<any>
const group$ = this.firestore.doc('/groups' + tempId).valueChanges();

You then have two options:

  1. Use group$.subscribe();
  2. Pass group$to the component you want and use the async pipe there

First option:

// In your component:
let data;
group$.subscribe(doc => {
  if (doc.exists) {
    data = doc
    console.log("Document data:", doc);   // No need to call data().
  } else {
    console.log("No such document!");
  },
  error => console.log("Error getting document:", error);
)

Second option, passing into the component where you would like the observable to be evaluated and the data displayed:

<div *ngIf="group$ | async as doc">
  Your html here {{ doc.someProperty }} further html here...
</div>

Personally, I prefer the second choice because it goes along with the framework nicely and keeps me from making asynchronous errors.

Have a look at angularfire2's Github repo for docs here. If there is no need to evaluate the observable by hand in code, I would not do this and let the framework handle it.

One final thing: If you use the observable and want to do some error handling when using the async pipe, you probably want to do so when creating the observable:

// Get Observable on document. Returns Observable<any>
// In error case, returns Observable of the error string
const group$ = this.firestore.doc('/groups' + tempId).valueChanges()
  .pipe(
    catchError(error => of(`Error getting document: ${error}`))
  );
thomi
  • 1,603
  • 1
  • 24
  • 31
  • ok so I tried according to your comments and it gave me an error in console like "Cannot read property 'subscribe' of undefined" – Virus Mar 22 '19 at 05:43
  • or How can I use async pipe in another component? – Virus Mar 22 '19 at 06:47
  • Actually the code you provided is in one method get(id: string) so what should I return in this method? and How to use this method in another component to get the data. – Virus Mar 22 '19 at 06:54
  • Sorry, had to update my code. See above. For passing, you define an @Input() variable in your other component and use the observable in your template ( e. g. ...content – thomi Mar 22 '19 at 09:37
  • In Updated code it gives error like "Property 'then' does not exist on type 'Observable" – Virus Mar 22 '19 at 17:05
  • I did a major edit. Should work like this. I would not use the get operator at all since you miss the realtime part of firestore that way. – thomi Mar 23 '19 at 06:46