3
votes

I have the following code in a component in my Angular2/Firebase app:

ngOnInit() {
  this.allItems = this.angularFireDatabase.object(`projects/${id}/results`).map(
    results => { 
      const x = itemMapper(results); 
      console.log(x); // <-- executes three times
      return x; 
  });

  const groupedData = this.allItems.map(groupMapper);
  this.towers = groupedData.map(towerMapper);
  this.units = groupedData.map(unitMapper);
}

I am using this.allItems, this.towers and this.units in the template with the async pipe:

<sanddance [items]="allItems | async">

<p-dataTable [value]="towers | async">
<p-dataTable [value]="units | async">

The code runs fine, but the problem is that the log executes three times--in other words, the mapping to create this.allitems is running three times. If I remove the this.towers and this.units assignments, then the log executes only once as expected. In other words, the this.towers = groupedData.map(towerMapper); line seems to be causing an extra call to the mapper creating this.allItems, although it shouldn't need to and I don't want to it. Could it even be re-accessing the data from the Firebase database, which would obviously be a problem, especially since this is a lot of data?

Based on my limited understanding on "hot" and "cold" observables, it seems that the AngularFire observable is being treated as "hot", and re-triggered when someone downstream tries to map it (or subscribe to it, which is essentially what the async pipe does).

What am I missing?

1

1 Answers

1
votes

The AngularFire2 observables are cold observables. Each subscription will see a separate Firebase Reference created internally - to which listeners are then attached, etc.

When the composed observables in your snippet are subscribed to, each will effect a subscription to the AngularFire2 object observable. If you want a single subscription to be used instead, you can use the share operator:

import 'rxjs/add/operator/share';

this.allItems = this.angularFireDatabase.object(`projects/${id}/results`).map(
  results => { 
    const x = itemMapper(results); 
    console.log(x); // <-- this should execute only once
    return x; 
}).share();

const groupedData = this.allItems.map(groupMapper);
this.towers = groupedData.map(towerMapper);
this.units = groupedData.map(unitMapper);

The share operator:

Returns a new Observable that multicasts (shares) the original Observable. As long as there is at least one Subscriber this Observable will be subscribed and emitting data. When all subscribers have unsubscribed it will unsubscribe from the source Observable. Because the Observable is multicasting it makes the stream hot. This is an alias for .publish().refCount().