How to read the length of a Firebase database

#1

Hi all, I’m pretty new to Ionic and I’m trying to create a to do list app with a Firebase database. I want the app to display the following when a list is empty:

<ion-item *ngIf="myItemList$.length==0">
      There are no items!
      <p>Click <ion-icon name="add-circle"></ion-icon> to add an item.</p>
    </ion-item>

but nothing is displayed.
This is my typescript for the list:

  myItemList$: Observable<Item[]>;
  constructor(public navCtrl: NavController, public navParams: NavParams, private modalCtrl: ModalController, public db: AngularFireDatabase,
     private itemListService: ItemListService, private toast: ToastController, private actionSheetCtrl: ActionSheetController) {
    
    this.myItemList$ = this.itemListService.getItemList().snapshotChanges().pipe(
      map(changes => 
        changes.map(c => ({
          key: c.payload.key, ...c.payload.val()
        }))
      ));

And this is my list provider:

private itemListRef = this.db.list<Item>
	 ('todoAppdb',ref=>ref.orderByChild("listid").equalTo(2));
	 constructor(private db: AngularFireDatabase){
	 }
	 getItemList() {
	 	return this.itemListRef;
	 }

I’m able to add and retrieve the items from firebase but when the list is empty the text is not displayed so I’m guessing I’m incorrectly reading the length of the database?

Any help would be appreciated, thanks!

#2

Hi @MarioRud :wave:

Your myItemList$ property is an Observable, so you need to use the async pipe to tell Angular to wait until its value is available:

<ion-item *ngIf="!(myItemList$ | async).length">

Best,
Rodrigo

#3

Hi Rodrigo, thank you for your reply!

I’ve tried using your suggestion but I get the error:

Uncaught (in promise): TypeError: Cannot read property ‘length’ of null
TypeError: Cannot read property ‘length’ of null
at Object.eval [as updateDirectives] (VM294 List1Page.ngfactory.js:225)

Any ideas on what could be causing this?

Thanks

#4

It looks like the returned value is null instead of of Item[] as you were expecting, so it fails because null doesn’t have a length property.

You can avoid these problems by using the safe navigation operator (?), so the template won’t try to get its length if the value is null:

<ion-item *ngIf="!(myItemList$ | async)?.length">

As to why the value returned from Firebase is null, maybe the document doesn’t exist or the path is not correct. Or maybe the document is not in the local cache and the first emitted value is null, but later comes the real value from the server. You’ll have to check the Angular Firebase documentation or debug what’s going on.

#5

Nothing against @FdezRomero here, whose explanation appears to be spot on, but I simply cannot look at the line noise that is:

!(myItemList$ | async)?.length

…and honestly say to myself “that template expression makes me happy”. Templates shouldn’t have to care about this arcana. They have lots of HTML business to bother with, and I always prefer to make their lives easier by letting them focus on that. What I would do instead is to have myItemList be a solid array property in the controller, initialized at declaration to [] (so it always has a valid and harmless length property). I would do all the work of getting things from Firebase and updating myItemList in the controller, so your template expression can go back to being the much more readable:

<ion-item *ngIf="myItemList.length === 0">
#6

@rapropos The async pipe is considered a best practice for working with observables in templates. Quoting from the Angular guide (https://angular.io/guide/pipes#the-impure-asyncpipe):

The Async pipe saves boilerplate in the component code. The component doesn’t have to subscribe to the async data source, extract the resolved values and expose them for binding, and have to unsubscribe when it’s destroyed (a potent source of memory leaks).

I understand you have the personal preference of doing all this initialization yourself, but the whole point of using Angular (and frameworks and platforms in general) is not having to re-invent the wheel and manually do these things.

Please reflect on your comment: it doesn’t help the OP to understand or fix the problem (since the null coming from Firebase would rewrite the empty array and fail at the template) and you miss to mention the weaknesses of your approach, like the observables needing to be unsubscribed when the component is to be destroyed to avoid memory leaks – which the async pipe does for you.

Best,
Rodrigo

1 Like
#7

Thank you for taking the time to comment.

The main point I want to emphasize here is the importance of separation of concerns. I often find myself creating bugs when I push too much responsibility downstream, and therefore I strive to do as much as far upstream as possible. To this end, I write from downstream upwards: I start with templates and ask what they would want to be simplest to write. Then up to controllers and what they want most from a service.

I like template expressions that do one thing. Two is my limit, and here we have three, including two that leak implementation details that I think belong further upstream:

  • get myItemList
  • unwrap it from an Observable
  • guard against it not being an array

I would consider this something to be solved in a service, unless the application needs to distinguish between null and an empty array for some reason.

I think it leaks implementation details. What if the next step is to make this page be able to add items to the list? If we’re going to keep the AsyncPipe around, we need to round-trip all the way to Firebase in order to make our addition. The users say that’s too sluggish and while we’re worrying about performance the client says that it also has to work offline, so now we have to cobble together a Subject that didn’t really need to exist in the first place.

I guess this is one of those situations where ultimately one has to make tradeoffs, and there are several defensible positions.

#8

@rapropos I’m not interested in getting into a debate on which solution is better. This user had a problem, I offered what I though was the easiest solution (less steps with the code posted) to achieve what he/she wanted following best practices, and he/she found a solution. That is what matters.

If you feel like rebating everything I wrote be my guest, I won’t add more noise to this thread. I think my point is clear: implementations are subjective and I tried to be as objective as possible, so this user can have his/her own opinions.

Best,
Rodrigo

#9

When I first read this tread I had the concern that too much data was being called from Firebase. If I understand correctly the method used would mean Firebase sending the complete collection down to the client.
The following might help: