Ionic Load Data from SQLite before Rendering

I have two functions in my application that retrieves data from SQLite, However, function A loads faster than function B. I tried putting LoadingController but it doesn’t work. Once the page loads, function A has already been rendered in the view before the Loader even begins. Then once the Loader is dismissed, I have to wait 3 seconds before function B fully renders to my view.

So the flow is

Page loads -> Function A fully renders to view -> Loader.present() -> Loader.dismiss() -> (wait 3 seconds) Function B renders

I’m not sure if this has something to do with my database but I only have a couple of data. I even tested it on a new phone but it is still happening.

This is my code:

database.ts

getPastExpensesSavings() {

      return this.database.executeSql("SELECT b.*, SUM(CASE WHEN es.type = 'expenses' AND es.date != strftime('%Y-%m-%d', DATE('now', 'localtime')) THEN 1 ELSE 0 END) number_of_expenses, IFNULL(SUM(CASE WHEN es.type='expenses' AND es.date != strftime('%Y-%m-%d', DATE('now', 'localtime')) THEN es.amount ELSE 0 END), 0) AS total_expenses, SUM(CASE WHEN es.type = 'savings' AND es.date != strftime('%Y-%m-%d', DATE('now', 'localtime')) THEN 1 ELSE 0 END) number_of_savings, IFNULL(SUM(CASE WHEN es.type='savings' AND b.budget_id Is NOT NULL AND es.date != strftime('%Y-%m-%d', DATE('now', 'localtime')) THEN es.amount ELSE 0 END), 0) AS total_savings, IFNULL(b.amount - (SUM(CASE WHEN es.type='expenses' AND es.date != strftime('%Y-%m-%d', DATE('now', 'localtime')) THEN es.amount ELSE 0 END) + SUM(CASE WHEN es.type='savings' AND b.budget_id IS NOT NULL AND es.date != strftime('%Y-%m-%d', DATE('now', 'localtime')) THEN es.amount ELSE 0 END)), b.amount) as remaining FROM budget AS b LEFT JOIN expenses_savings AS es ON b.budget_id = es.budget_id WHERE b.budget_id IS NOT NULL GROUP BY b.budget_id ORDER BY b.budget_id DESC", {})
        .then(data => {
          
          this.expenses_savings = [];
         
          if(data.rows.length > 0) {

            for(var i = 0; i < data.rows.length; i++) {

              this.expenses_savings.push({

                budget_id: data.rows.item(i).budget_id,
                amount: data.rows.item(i).amount,
                valid_from: data.rows.item(i).valid_from,
                valid_to: data.rows.item(i).valid_to,
                total_expenses: data.rows.item(i).total_expenses,
                total_savings: data.rows.item(i).total_savings,
                number_of_savings: data.rows.item(i).number_of_savings,
                number_of_expenses: data.rows.item(i).number_of_expenses,
                remaining: data.rows.item(i).remaining,
                details: []

              });

            }

          }

          return this.expenses_savings;

        }, err => {

          return err;

        })
    }

getPastSavingsExpensesDetails() {

      return this.database.executeSql("SELECT * FROM expenses_savings WHERE date != strftime('%Y-%m-%d', DATE('now', 'localtime')) AND budget_id IS NOT NULL ORDER BY date DESC, time DESC", {})
        .then(res => {

          this.expenses_savings_details = [];
          
          if(res.rows.length > 0) {

            for(var i = 0; i < res.rows.length; i++) {

              this.expenses_savings_details.push({

                budget_id: res.rows.item(i).budget_id,
                type: res.rows.item(i).type,
                category: res.rows.item(i).category,
                title: res.rows.item(i).title,
                date: res.rows.item(i).date,
                time: res.rows.item(i).time,
                datetime: res.rows.item(i).date + ' ' + res.rows.item(i).time,
                amount: res.rows.item(i).amount,
                note: res.rows.item(i).note,
                image: res.rows.item(i).image

              });

            }
          
          }

          return this.expenses_savings_details;

        }, err => {

          return err;

        })
   
    }

page.ts

ionViewWillEnter() {

 
    this.getPastExpensesSavings();
    
  }

getPastExpensesSavingsDetails() {

    this.dp.getPastSavingsExpensesDetails()
      .then(data => {

        this.pastExpensesSavingsDetails = data;

      })
  }

getPastExpensesSavings() {

    this.dp.getPastExpensesSavings()
      .then(data => {

        let loading = this.loadingCtrl.create({
          content: 'Loading data. Please wait...'
        });

        loading.present();

        if(data.length == 0){

          this.hasPastExpenses = false;

        }else {
          
          this.hasPastExpenses = true;
          this.pastExpensesSavings = data;

          this.dp.getPastSavingsExpensesDetails()
            .then(data => {

              this.pastExpensesSavingsDetails = data;

              for(var es = 0; es < this.pastExpensesSavings.length; es++) {

                for(var esd = 0; esd < this.pastExpensesSavingsDetails.length; esd++) {

                  if(this.pastExpensesSavings[es].budget_id == this.pastExpensesSavingsDetails[esd].budget_id) {

                    this.pastExpensesSavings[es].details.push(this.pastExpensesSavingsDetails[esd])

                  }

                }

              }

            })
          
        }

        loading.dismiss();
       
      })

  }

page.html

<ion-list *ngFor="let item of pastExpensesSavings">

			<accordion [list]="item"></accordion> 

			<div class="test" *ngIf="item.details.length > 0">
				
			    <ion-item *ngFor="let detail of item.details" (click)="showDetails(detail.expenses_savings_id)">
			    	
			    	<ion-icon class="card-icon" item-start [name]="detail.type == 'expenses' ? 'trending-up' : 'cash' " [color]="detail.type == 'expenses' ? 'danger' : 'custom'"></ion-icon>
			      	<h2>{{ detail.title }}</h2>
			      	<p>{{ detail.amount | number:'.2-2' }}</p>

			      	<p *ngIf="detail.type == 'expenses'" class="time-ago" item-end>Spent {{ detail.datetime | timeAgo }}</p>

			      	<p *ngIf="detail.type == 'savings'" class="time-ago" item-end>Added {{ detail.datetime | timeAgo }}</p>
      	
			    </ion-item>

			</div>

			<p text-center *ngIf="!item.details.length">No past records to show</p>

	  	</ion-list>

Is there a way I can make sure the the Loader stays until the data are fully retrieved and ready for render?

Thank you very much!

I’d change getPastExpensesSaving to something like this:

getPastExpensesSavings() {
    let loading = this.loadingCtrl.create({
        content: 'Loading data. Please wait...'
    });

    loading.present();

    this.dp.getPastExpensesSavings()
    .then(data => {
        this.hasPastExpenses = data.length > 0;
        if(data.length == 0) {
            return false;
        }

        this.hasPastExpenses = true;
        this.pastExpensesSavings = data;

        return this.dp.getPastSavingsExpensesDetails();
    })
    .then(data => {
        if(data) {
            this.pastExpensesSavingsDetails = data;

            for(var es = 0; es < this.pastExpensesSavings.length; es++) {
                for(var esd = 0; esd < this.pastExpensesSavingsDetails.length; esd++) {
                    if(this.pastExpensesSavings[es].budget_id == this.pastExpensesSavingsDetails[esd].budget_id) {
                        this.pastExpensesSavings[es].details.push(this.pastExpensesSavingsDetails[esd])
                    }
                }
            }
        }

        loading.dismiss();
    });
}

Which no longer nests your promises, as well as waits for both to resolve before calling dismiss on your Loader.

Ok, that worked!

But I do not understand why mine did not work. Could you explain it a little further? I’m kinda new to Angular and I am still learning.

Thank you very much!

Certainly.

So a simplified structure of your original code was this:

Promise(getPastExpensesSavings) -> savings
  Loader.Show
  Use savings
  Promise(getPastSavingsExpensesDetails) -> details
    Use details
  Loader.Dismiss

So as you mentioned, Promises are asynchronous. They’re called Promises for a reason, it will complete its work at some point (it promises, after all) but not right this second. So the issue was that you started off the second promise (getPastSavingsExpensesDetails) and then dismissed the Loader, without waiting for the second promise to finish.

So an “alternate” solution would be to add loader.dismiss after your for loops, i.e.

for(var es = 0; es < this.pastExpensesSavings.length; es++) {
    for(var esd = 0; esd < this.pastExpensesSavingsDetails.length; esd++) {
        if(this.pastExpensesSavings[es].budget_id == this.pastExpensesSavingsDetails[esd].budget_id) {
            this.pastExpensesSavings[es].details.push(this.pastExpensesSavingsDetails[esd])
        }
    }
}

loading.dismiss();

As this would then wait for both promises to complete.

However then you’d also need to add:

if(data.length == 0){
    this.hasPastExpenses = false;
    loading.dismiss();
}

And now you’re (unnecessarily) duplicating code, and you still have nested promises. Nested promises are considered to be poor form, if just because they unnecessarily complicate code and often swallow up errors and the like.

Ok, now I get it!

I was executing the second promise inside the first one.

I will keep that in mind.

Thank you very much, kind sir! :slight_smile:

Hello,
I. m a bloddy beginner in javascript and the whole web stuff, but thanks to @AaronSterling I learned today, if you are waiting for two ore more independed promises, it is maybe a good idea using
promise.all([apromise, anotherpromise]) .then
That waits for all promises in that array, before it execute then
Look here How to return the result of this function

Best regards, anna-liebt

Ps should go to @mhtchong

Indeed, Promise.all is incredibly useful. In this case I think it’s just up to personal taste, as I wouldn’t utilize Promise.all just because the second promise depends on the result of the first one.

Hello,
yes indeed, if they depend on it.

Today I stumbled upon 2 questions, where this was very helpful. So for me it was a helpful learning, that Do not nest promise, chain it and if they are independed use promise.all.

This should a hint to @mhtchong.

Best regards, anna-liebt