0

I'm having such a hard time using the cloud storage from firebase with Angular that I end up in a dead end.

I'm trying to do something simple: retrieving the data from the database and saving it in a class variable for further processing.

races: Race[]
ngOnInit() {
    this.races = []

    this.userResolver.getUserInfo().then(
      res => {
        this.userInfo = res
        let docRef = this.db.collection('/players/').doc(this.userInfo.uid).ref
        docRef.get().then(function(doc) {
          if (doc.exists) {
              console.log("Document data:", doc.data());
              let res = doc.data().races
              console.log("res:", res);
              res.forEach(item=>{
                console.log("item:", item);
                console.log(item.id);
                let race = new Race(item.id, item.title, item.progress, item.thumbnail)
                console.log(race);
                this.races.push(race)
              })
              console.log(this.races);

          } else {
              console.log("No such document!");
          }
      }).catch(function(error) {
          console.log("Error getting document:", error);
      });
    }

And here is the console log:

Document data: {races: Array(2)}
res: (2) [{…}, {…}]
item: {id: "Whu855ZbOsIM9SoIE6pu", progress: 50, thumbnail: "", title: "Brasil"}
(id) Whu855ZbOsIM9SoIE6pu
Race {id: "Whu855ZbOsIM9SoIE6pu", progress: 50, thumbnail: "", title: "Brasil"}
Error getting document: TypeError: Cannot read property 'races' of undefined
at menu.component.ts:39
at Array.forEach (<anonymous>)
at menu.component.ts:34
at ZoneDelegate.push../node_modules/zone.js/dist/zone.js.ZoneDelegate.invoke (zone.js:388)
at Object.onInvoke (core.js:3820)
at ZoneDelegate.push../node_modules/zone.js/dist/zone.js.ZoneDelegate.invoke (zone.js:387)
at Zone.push../node_modules/zone.js/dist/zone.js.Zone.run (zone.js:138)
at zone.js:872
at ZoneDelegate.push../node_modules/zone.js/dist/zone.js.ZoneDelegate.invokeTask (zone.js:421)
at Object.onInvokeTask (core.js:3811)

Actually, I tried to change the member of the class to a variable declarated in the function, (let races for example) and that works, but in the moment I assigne it to the class member it doesn't. It looks like inside the promise, it doesn't know 'this' because I created another class variable and when I try to print it, I get the same error.

I have managed to retrieve the data from Firebase if it's directly from a collection with this code:

this.db.collection('/cards')
    .valueChanges()
    .subscribe(res => {
        this.cards = res.map(card=> card as Card);

However, when I want to filter the request and select only one player between all players, I can't make it work. I do get the data but I can't save it or cast it to my interface.

I would appreciate any help. I have tried everything I have seen but actually the doc of Firebase for Angular is quite bad.

Please, read my problem and avoid commenting with something like collection('players') and that's all. I need to select the player inside the collection.

Thank you

-- Fix (thanks to Renaud Tarnec):

let docRef = this.db.collection('/players/').doc(this.userInfo.uid).ref;
docRef.get().then(doc => {
            if (doc.exists) {
                let res = doc.data().races
                res.forEach(item=>{
                  this.races.push(item as Race)
                })
            } else {
                console.log("No such document!");
            }
        }).catch(function(error) {
            console.log("Error getting document:", error);
        });

Also, I changed my class Race to an Interface to avoid calling the constructor.

Javier
  • 117
  • 1
  • 9

1 Answers1

3

I am not at all a specialist of angular but I think you are having a problem of context with this inside the function passed as handler to docRef.get().then().

By using the arrow syntax you should solve it. Do as follows:

...
docRef.get().then(doc => {
          if (doc.exists) {
              console.log("Document data:", doc.data());
              let res = doc.data().races
              console.log("res:", res);
              res.forEach(item=>{
                console.log("item:", item);
                console.log(item.id);
                let race = new Race(item.id, item.title, item.progress, item.thumbnail)
                console.log(race);
                this.races.push(race)
              })
              console.log(this.races);

          } 
...

See this SO question and answer for more details: Arrow function vs function declaration / expressions: Are they equivalent / exchangeable?

You could also solve it as follows:

races: Race[]
ngOnInit() {
    const self = this;
    self.races = []

    self.userResolver.getUserInfo().then(
      res => {
        self.userInfo = res;
        let docRef = self.db.collection('/players/').doc(self.userInfo.uid).ref
        docRef.get().then(function(doc) {
          if (doc.exists) {
              console.log("Document data:", doc.data());
              let res = doc.data().races
              console.log("res:", res);
              res.forEach(item=>{
                console.log("item:", item);
                console.log(item.id);
                let race = new Race(item.id, item.title, item.progress, item.thumbnail)
                console.log(race);
                self.races.push(race)
              })
              console.log(self.races);

          } else {
              console.log("No such document!");
          }
      }).catch(function(error) {
          console.log("Error getting document:", error);
      });
    }

In this case, "self is being used to maintain a reference to the original this even as the context is changing", see What underlies this JavaScript idiom: var self = this?

Renaud Tarnec
  • 53,666
  • 7
  • 52
  • 80
  • 1
    Thank you so much, It was totally that. I was so focus on the firebase problem I had before that I didn't even realize I changed the arrow for a function. Now it works perfectly even with my class race as Interface. I'll update the question. – Javier Oct 22 '18 at 12:09