Torbjorn Zetterlund

Sun 30 2018
Image

How to fix object key show undefined after Angular/Firebase library upgrade

by bernt & torsten

I’ve been working on an Angular Ionic CordovaFirebase mobile application, recently it was time to upgrade Angular and Firebase libraries to a newer version. I upgraded Angular from 4.x to 7.x and Angular Firebase from 3.9 to 5.x – while I was doing this, I run into a problem with the object key, I will explain what the issue was and how I fixed it.

To be clear, I’m not going to explain the steps on how to upgrade from Angular 4 to 7 – there is a guide already for that which you can find here – https://update.angular.io/

First the issue about the object key, I have a feature in my mobile application that allows users to check what pictures they submitted through the mobile application, the images stored in the Firebase Storage and the extra metadata and user information that is collected are stored in the Firebase Realtime Database.

Code before upgrade of Angular/Firebase libraries

In the display image feature in my mobile application, the images are displayed and are clickable. So I need the key value to be able to either click to see detail view for an image or to delete an image. To do this I create a code snipped that access Firebase Realtime Database with a query and get the key value for each image to display.

This is how the application display image feature looks like on a mobile.

Before I updated Angular and Firebase libraries, my code to query firebase and get the result back looked like this:

import {Component} from '@angular/core';
import {IonicPage, NavController, ToastController} from 'ionic-angular';
import {AngularFireDatabase} from 'angularfire2/database';
import {AngularFireAuth} from 'angularfire2/auth';
import { GoogleAnalytics } from '@ionic-native/google-analytics';

@IonicPage()
@Component({
 selector: 'page-mypictures',
 templateUrl: 'mypictures.html',
})

export class MypicturesPage {
 mypictures: any[] = [];

 constructor(public navCtrl: NavController,
             public toastCtrl: ToastController,
             public ga: GoogleAnalytics,
             public af: AngularFireAuth,
             public db: AngularFireDatabase) {

   this.googleanalyticstrack();

   if (this.af.auth.currentUser) {
     // Find specific records of a user
     this.db.list('rubbish', {
       query: {
         orderByChild: 'userId',
         equalTo: this.af.auth.currentUser.uid
       }
     }).subscribe(
       data => {
         this.mypictures = data;
       }
     );

   }
 }
 /**
 * --------------------------------------------------------------
 * Google Analytics
 * --------------------------------------------------------------
 */
 googleanalyticstrack() {
   this.ga.startTrackerWithId('UA-xxxxxxxx-x')
     .then(() => {
       console.log('Google analytics is ready now');
       this.ga.trackView('My picture');
       this.ga.trackEvent('My picture', 'event', 'My Picture', 1);
     })
     .catch(e => console.log('Error starting GoogleAnalytics', e));
 }

 isMypictures(): boolean {
   if (this.mypictures.length == 0) {
     return false;
   } else {
     return true;
   }
 }

 litterDetails(key) {
   this.navCtrl.push("MypicturesDetailPage", {
     key: key
   })
 }

 removeMypicture(key) {
   if (this.af.auth.currentUser) {
     this.db.object('/rubbish/' + key).remove();
   }
 }

 createToaster(message, duration) {
   let toast = this.toastCtrl.create({
     message: message,
     duration: duration
   });
   toast.present();
 }
}

The bold and inverted text shows the code to query the firebase and subscribe to the result. And here is how the .html file looks like:

<ion-header>
 <ion-navbar hideBackButton="true">
   <button ion-button menuToggle>
     <ion-icon name="menu"></ion-icon>
   </button>
   <ion-title>Litter App</ion-title>
 </ion-navbar>
</ion-header>

<ion-content [ngClass]="{bg:!isMypictures()}">
 <div *ngIf="!isMypictures()">
   <h4>Your Submissions!</h4>
 </div>

 <ion-card class="category" *ngIf="isMypictures()">
   <div *ngFor="let mypicture of mypictures">
     <div (click)="litterDetails(mypicture.$key)">
       <div class="image">
         <img src={{mypicture.imgUrl}}>
         <div class="bg-overlay"></div>
       </div>
     </div>
     <ion-card-content>
       <ion-row>
         <ion-col col-11>
           <p>{{mypicture.brand}}</p>
         </ion-col>
         <ion-col col-1>
           <ion-icon (click)="removeMypicture(mypicture.$key)" class="bookmark" name="trash"></ion-icon>
         </ion-col>
       </ion-row>
     </ion-card-content>
   </div>
 </ion-card>
</ion-content>

The bold and inverted text above shows how the $key is used to make the image clickable with the right object key value.

After Angular/Firebase library update

After the upgrade, I made some changes to my code, as the newer library of Firebase has some major changes that made my original code to not work out after the upgrade.

I could not use .subscribe alone as a method to get the data, I needed to add either .valuChanges() or .snapshotChanges() method, you can read more about them here.

In the following code example, I used .snapshotChanges() as it would give me the data object and object key, using .valueChanges() just gives me the object data in .json format without the object key. I also had to change how the firebase query had worked, as you can see from the code example below:

import {Component} from '@angular/core';
import {IonicPage, NavController, ToastController} from 'ionic-angular';
import {AngularFireDatabase, AngularFireList} from '@angular/fire/database';
import {AngularFireAuth} from '@angular/fire/auth';
import {GoogleAnalytics} from '@ionic-native/google-analytics';
import { map } from 'rxjs/operators/map';

@IonicPage()
@Component({
 selector: 'page-mypictures',
 templateUrl: 'mypictures.html',
})

export class MypicturesPage {
 mypictures: any[] = [];

 myPicturesRef: AngularFireList<any>;

 constructor(public navCtrl: NavController,
             public toastCtrl: ToastController,
             public ga: GoogleAnalytics,
             public af: AngularFireAuth,
             public db: AngularFireDatabase) {

   this.googleanalyticstrack();

   if (this.af.auth.currentUser) {
     // Find specific records of a user
     this.myPicturesRef = db.list('/rubbish', ref => ref
         .orderByChild('userId')
         .equalTo(this.af.auth.currentUser.uid));
     this.myPicturesRef
       .snapshotChanges().subscribe((res) => {
       this.mypictures = res.map(change => ({key: change.payload.key, ...change.payload.val()}));
    });

   }
 }

 /**
 * --------------------------------------------------------------
 * Google Analytics
 * --------------------------------------------------------------
 */
 googleanalyticstrack() {
   this.ga.startTrackerWithId('UA-xxxxxx-x')
     .then(() => {
//        console.log('Google analytics is ready now');
       this.ga.trackView('My picture');
       this.ga.trackEvent('My picture', 'event', 'My Picture', 1);
     })
     .catch(e => console.log('Error starting GoogleAnalytics', e));
 }

 isMypictures(): boolean {
   if (this.mypictures.length == 0) {
     return false;
   }
   else {
     return true;
   }
 }

 litterDetails(key) {
   console.log("key for the image:" + key);
   this.navCtrl.push("MypicturesDetailPage", {
     key: key
   })
 }

 removeMypicture(key) {
   if (this.af.auth.currentUser) {
     this.db.object('/rubbish/' + key).remove();
   }
 }

 createToaster(message, duration) {
   let toast = this.toastCtrl.create({
     message: message,
     duration: duration
   });
   toast.present();
 }
}

The bold and inverted text is what changed. I also had to make a small change to my HTML, instead of using $key I just use key as shown in the code below in the bold and inverted text.

<ion-header>
 <ion-navbar hideBackButton="true">
   <button ion-button menuToggle>
     <ion-icon name="menu"></ion-icon>
   </button>
   <ion-title>Litter App</ion-title>
 </ion-navbar>
</ion-header>

<ion-content [ngClass]="{bg:!isMypictures()}">
 <div *ngIf="!isMypictures()">
   <h4>Your Submissions!</h4>
 </div>
 <ion-card class="mypicture" *ngIf="isMypictures()">
   <div *ngFor="let mypicture of mypictures">
     <div (click)="litterDetails(mypicture.key)">
       <div class="image">
         <img src={{mypicture.imgUrl}}>
         <div class="bg-overlay"></div>
       </div>
     </div>
     <ion-card-content>
       <ion-row>
         <ion-col col-11>
           <p>{{mypicture.brand}}</p>
         </ion-col>
         <ion-col col-1>
           <ion-icon (click)="removeMypicture(mypicture.key)" class="bookmark" name="trash"></ion-icon>
         </ion-col>
       </ion-row>
     </ion-card-content>
   </div>
 </ion-card>
</ion-content>

Conclusion

Upgrades should be simple, and should not break the code. I’m not for large changes that make you debug your code all over to fix problems, an upgrade should just go smoothly. That said, my code is now working great and I hope that this may help someone with a similar problem.

Share: