Refreshing tab UI with Firebase Observable

Ok I am at the end of my rope I admit it! I have looked at this for days and have made no progress. As I have alluded to in a few posts I am writing an app which allows the user to click a button to load in data from Firebase (which works fine) and then the appropriate information is then displayed over 3 tabs. This all works ok…until I change the data from the database. As it stands the new data is downloaded but never refreshed in the tabs - unless I click my refresh button which then loads the data. I have tried so many iterations of current implementation/map/subscribe etc… and now I am at a loss. Any help would be very much appreciated. (much of the code below is stubs and the full implementation has been removed to isolate the firebase refresh UI issue. I have also only included the home tab…as if I can refresh on 1 then I can refresh on all). I know I could use ionViewWillEnter, but if they are already on that tab then loading a new data set should automatically update that tab.

app.html

<ion-header>
  <ion-fab bottom right edge>
     <button ion-fab mini (click)="newBarcode()"><ion-icon name="camera"></ion-icon></button>
  </ion-fab>
  <ion-navbar>
    <ion-title>OVC</ion-title>

  </ion-navbar>    
  <ion-toolbar color="twitter" class="sub-header">
      Loaded: {{ loadedRelicId }}
  </ion-toolbar>
</ion-header>

<ion-nav [root]="rootPage" class="has-header"></ion-nav>

app.component.ts

import { Component } from '@angular/core';
import { Platform } from 'ionic-angular';
import { StatusBar } from '@ionic-native/status-bar';
import { SplashScreen } from '@ionic-native/splash-screen';

import { TabsPage } from '../pages/tabs/tabs';
import { BarcodeScanner } from '@ionic-native/barcode-scanner';
import { GlobalVarsProvider } from '../providers/global-vars/global-vars';

@Component({
  templateUrl: 'app.html'
})
export class MyApp {
  rootPage:any = TabsPage;
  loadedRelicId: string;
    
  constructor(platform: Platform, statusBar: StatusBar, splashScreen: SplashScreen, private barcodeScanner: BarcodeScanner, private gvp: GlobalVarsProvider) {
    platform.ready().then(() => {
      statusBar.styleDefault();
      splashScreen.hide();
    });
  }
    
  newBarcode(){
      this.gvp.updateRelic("olaf");
      this.loadedRelicId = this.gvp.getRelic();
  }    
}

home.html

<ion-content padding>
    <ion-card>
        <ion-card-content>
            <h2>Welcome to XXXXXX</h2>
    <h3>Using the facilities</h3> 
    <p>
    This starter project comes with simple tabs-based layout for apps
    that are going to primarily use a Tabbed UI.
  </p>
  <p>
    Take a look at the <code>src/pages/</code> directory to add or change tabs,
    update any existing page or create new pages.
  </p>
        </ion-card-content>
    </ion-card>
<ion-list>
  <ion-item class="text" *ngFor="let item of items | async">
    {{item | json }}
  </ion-item>
  </ion-list>
  <button ion-button (click)="refreshMe()" >refresh</button>
</ion-content>

home.ts

import { Component, ChangeDetectionStrategy, NgZone } from '@angular/core';
import { NavController } from 'ionic-angular';
import { GlobalVarsProvider } from '../../providers/global-vars/global-vars';
import 'rxjs/add/operator/map';
import {Observable} from 'rxjs/Observable';
import { FirebaseListObservable } from 'angularfire2/database';

@Component({
  selector: 'page-home',
  templateUrl: 'home.html',
  changeDetection: ChangeDetectionStrategy.Default
})
export class HomePage {
  items: FirebaseListObservable<any[]>;
    
  constructor(public navCtrl: NavController, private gvp: GlobalVarsProvider, public zone: NgZone) {

      

  }
    
    ionViewWillEnter() {
        console.log("1");
    }
    
  ionViewDidLoad() {
      this.items = this.gvp.items;
  }
    
    refreshMe(){
       this.items = this.gvp.returnData(); 
    }
    

}

global-vars.ts

import { Injectable } from '@angular/core';
import { Http } from '@angular/http';
import 'rxjs/add/operator/map';
import { Observable } from 'rxjs';
import { AngularFireDatabase, FirebaseListObservable } from 'angularfire2/database';

@Injectable()
export class GlobalVarsProvider {

    public items: FirebaseListObservable<any[]>;
    loadedRelic: string;
    
    
    constructor(public http: Http, public afd: AngularFireDatabase) {
        this.loadedRelic = "magnus";
        this.loadData();

    }
    

    
    loadData(){
        this.items = this.afd.list('/' + this.loadedRelic);

    }
    
    updateRelic(item: string){
        this.loadedRelic = item;
        this.loadData();
    }
    
    getRelic(){
        return this.loadedRelic;
    }
    
    returnData(): FirebaseListObservable<any[]>{
        return this.items;
    }

}

many thanks
M

Sweet lord I have finally fixed it! :smiley: this is why I love to hate coding…the depths of despair to the heights of joy in the space of a few mins! :slight_smile:

set a timeout in global-vars.ts and used a subscribe in home.ts. Its a little slower than before but works! :slight_smile: happy to supply code if it is useful for anyone.

As a stylistic point, I’d rename global-vars to what the provider is actually providing, like RelicDataProvider. Same thing with items, etc.

In the provider:

private _relicDataStream: FirebaseListObservable<any> = new FirebaseListObservable<any>();

this._relicDataStream = this.angularFireDB.list(referenceLocation);

relicDataStream(): Observable<desiredItemType> {
   return <Observable<desiredItemType>> this._relicDataStream;
}

In the constructor of your page:
this.relicsStream = this.providerName.relicDataStream();
You might need to tweak this depending on your device, because Angular doesn’t behave the same on all devices. But the basic thing you’re doing wrong is that you’re using your provider like a Java getter, instead of like a reactive stream. Note that I’ve set things up so your items variable is a stream. The async pipe subscribes to (and unsubscribes from) streams.

Edited to fix typo in code.

Cheers Aaron - can you tell I am historically a Java coder then lol :smiley:

yeah the names are terrible at the moment, the classes have changed so many times that the naming convention was the last thing on my mind lol. However, just spent the last 15 mins updating them to appropriate names :smiley:

good shout on the device specifics. Will look into this. I am still fairly new to Ionic but more so to this side of it!

cheers
M

I don’t always do this next “rule” but usually: I put the word “Stream” into the name of Observables somewhere, and put the word “Snapshot” into the name of Observable.take(1). Separates them out from more traditional variables.

this code causes errors:

Supplied parameters do not match any signature of call target.

    private _relicDataStream: FirebaseListObservable<any> = new FirebaseListObservable<any>();

taking it out sorted it

Using raw types like that is a pennywise pound foolish fix. Maybe it compiles today, but if you don’t type your stream, you’re asking for bug city if you want to expand past two pages.

Im all confused now. This is my current code: It updates the current tab fine but any preloaded (ie previously visited tabs) wont update unless I add the ionViewDidEnter.

global-vars (yeah yeah I know lol)

@Injectable()
export class GlobalVarsProvider {

    private _relicDataStream: FirebaseListObservable<any[]>;
    private _loadedRelic: string;    
    private _getDataObserver: any;
    public getData: any;
    
    
    constructor(public http: Http, public afd: AngularFireDatabase) {
        this._loadedRelic = "";
    
        this._getDataObserver = null;
        this.getData = Observable.create(observer => {
            this._getDataObserver = observer;
        });
    }
    
    private loadData(){
        this._relicDataStream = this.afd.list('/' + this._loadedRelic);
        console.log("Reading Firebase");
    }
    
    public getRelicData() {
        if (this._loadedRelic != ""){
          this.loadData();
          setTimeout(() => {
            this._getDataObserver.next(this._relicDataStream);
          }, 1000);
        }
    }
    
    public relicDataStream(): FirebaseListObservable<any[]> {
        return <FirebaseListObservable<any[]>> this._relicDataStream;
    }
    
    updateRelic(item: string){
        if (item != this._loadedRelic){
            this._loadedRelic = item;
            this.getRelicData();
        }
    }
    
    getRelicId(){
        return this._loadedRelic;
    }

}

**app.component **

export class MyApp {
  rootPage:any = TabsPage;
  loadedRelicId: string;
    
  constructor(platform: Platform, statusBar: StatusBar, splashScreen: SplashScreen, private barcodeScanner: BarcodeScanner, private gvp: GlobalVarsProvider) {
    platform.ready().then(() => {
      statusBar.styleDefault();
      splashScreen.hide();
    });
    console.log("App init");  
    this.gvp.updateRelic("magnus");
    this.loadedRelicId = this.gvp.getRelicId();
  }
    
  newBarcode(){
      if (this.gvp.getRelicId() == "magnus")
        this.gvp.updateRelic("olaf");
      else
        this.gvp.updateRelic("magnus");  
      this.loadedRelicId = this.gvp.getRelicId();
  }
}

info.ts

export class InfoPage {
  private items: FirebaseListObservable<any[]>;
    
  constructor(public navCtrl: NavController, private gvp: GlobalVarsProvider) {
     console.log("Init info");
       
     this.gvp.getRelicData(); 
     this.gvp.getData.subscribe((items) => {
       this.items = items;
     });
  }
    
  ionViewDidEnter() {
     console.log("Enter info"); 
     this.items = this.gvp.relicDataStream();
  }

}

video.ts

export class VideoPage {
  items: FirebaseListObservable<any[]>;
    
  constructor(public navCtrl: NavController, private gvp: GlobalVarsProvider) {
     console.log("Init video");
      this.gvp.getRelicData();
      
     this.gvp.getData.subscribe((items) => {
       this.items = items;
     });
  }
    
  ionViewDidEnter() {
     console.log("Enter info"); 
     this.items = this.gvp.relicDataStream();
  }

}

audio.ts is the same. This solution does work (most of the time) but it seems so inelegant! I would have thought that once subscribed and with a timeout all pages which have been previously visited (and hence in memory) would have kept an active listener to the observable? Am I missing something obvious or am I tied to updating via ionViewDidEnter?

btw, I very much appreciate all of your help here!
M