ngIf not working on first load

I’m still new to Ionic but I have started to get the grips of it, however I’m experiencing some odd behaviour and not sure what’s causing it.

I’m using ngif to toggle some content on the page, but in almost always when I open the page for the first time the events don’t fire correctly, if I pop the page from the stack and re-open it everything works smooth as butter.

In the below example I’m expecting the item which has the following condition: <ion-item *ngIf=!havelocation> to be removed when the variable havelocation is set to true. I can see from the logs that it does change the value to true, but the item remains visible. If I navigate away from the page, and then open the page again, it works as expected.

I have tried to move the code from constructor to ionViewDidLoad, to add timeout but no joy, even wrapped the whole code on page.ready but still the same result.

I get no error at all during compiling or in Javascript console so I’m not sure what I’m doing wrong.

Any suggestion/hint please?

Thank you.

The HTML:

<ion-header>
  <ion-navbar>
  
  
    <ion-title>
    Job Card
    </ion-title>

  </ion-navbar>
</ion-header>

<ion-content style='background:green;'>
<h2 style='text-align:center;color:white;'>{{company}}</h2>
	<ion-card>
	
		<ion-item>
		<ion-icon name="information-circle" item-left large balanced></ion-icon>
		<h3>{{short_address}}</h3>
		<p style='overflow:auto;white-space:normal;'>{{long_address}}</p>
		</ion-item>
		<ion-item>
		<ion-icon name="information-circle" item-left large></ion-icon>
		<h3>{{short_description}}</h3>
		<p style='overflow:auto;white-space:normal;'>{{long_description}}</p>
		</ion-item>
		
		<ion-item *ngIf=showrisk>
		<ion-icon name="warning" item-left large style='color:red;'></ion-icon>
		<h3>Risk Assesment</h3>
		<p style='overflow:auto;white-space:normal;'>{{risk_title}}</p>
			 <button ion-button outline item-right icon-left (click)="itemSelected()">
			<ion-icon name="eye"></ion-icon>
			View
			</button>
		</ion-item>
	
	<ion-item *ngIf=!havelocation>
	 <ion-spinner item-left></ion-spinner>
	 <p>Loading route...</p>
	</ion-item>
	
	<ion-item  *ngIf="havelocation && !iserror">
		
		<span item-left [ngClass]="{'my_green': step=='1','my_orange': step=='2','my_red': step=='3'}">{{traveltime}}</span>
		<span item-left>({{distance}})</span>
    
	<button ion-button icon-left clear item-right (click)="Startnagivation()">
      <ion-icon name="navigate"></ion-icon>
      Start
    </button>
	</ion-item>
	
	<ion-item *ngIf="havelocation && iserror">
	 <p>Could not load route...</p>
	</ion-item>
	
	</ion-card>


</ion-content>

And this is the TS file:

declare var window: any;
import { Component } from '@angular/core';
import { NavController, AlertController, NavParams, Platform   } from 'ionic-angular';
import { Geolocation } from 'ionic-native';
import {InAppBrowser} from 'ionic-native';
import {Http} from '@angular/http';
import { LaunchNavigator,LaunchNavigatorOptions  } from 'ionic-native';




@Component({
  selector: 'job_card',
  templateUrl: 'job_card.html'
})
export class JobCard {
	
	havelocation:boolean=false;
	showrisk:boolean=false;
	iserror:boolean=false;
	distance;
	traveltime;
	company;
	short_address;
	long_address;
	short_description;
	long_description;
	risk_title;
	risk_pdf;
	store_lat;
	store_lng;
	step;
	jobid;
	
    constructor(private platform: Platform ,private navController: NavController, public params:NavParams, private http:Http) {
			this.jobid=params.get("jobid");
			this.http = http;
	}
ionViewDidLoad () {
		this.step='1';

		var aux=this;
	
		
		
		
			var db = window.openDatabase('mydb', '1.0', 'MyDB', 2 * 1024 * 1024);
				db.transaction(function(tx){
					var query='SELECT MYWORK.*,JOBDESC.short_desc,JOBDESC.long_desc,RISKDEF.title,RISKDEF.pdf from MYWORK '+
					'LEFT JOIN JOBDESC on JOBDESC.id=MYWORK.job_description '+
					'LEFT JOIN RISKDEF on RISKDEF.id=MYWORK.risk WHERE scheduleid=?';
							tx.executeSql(query, [aux.jobid], function(tx, results) {
								
								var row = results.rows.item(0);
								aux.company=row['Comp_Name'];
								aux.short_address=row['Short_add'];
								aux.long_address=row['Long_add'];
								aux.short_description=row['short_desc'];
								aux.long_description=row['long_desc'];
								if (row['pdf'])
									{
										aux.risk_title=row['title'];
										aux.risk_pdf=row['pdf'];
										aux.showrisk=true;
									}
								aux.store_lat=row['Latitude'];
								aux.store_lng=row['Longitude'];
								aux.CalculateDistance();
									
							},function(tx,error){
								console.log(error);
							}); 

				});
		
	}
	
CalculateDistance()
{				
		
			Geolocation.getCurrentPosition().then((position) => {
				
				 
				 this.http.get(' https://maps.googleapis.com/maps/api/directions/json?origin='+position.coords.latitude+','+position.coords.longitude+'&destination='+this.store_lat+','+this.store_lng+'&traffic_model=best_guess&departure_time=now&units=imperial&key=MYKEY')
								  .map(res => res.json())
								  .subscribe(
									data => {
										console.log(data);
										if (data.status=="OK")
										{
											this.distance=data.routes[0].legs[0].distance.text;
											this.traveltime=data.routes[0].legs[0].duration_in_traffic.text;
											if (data.routes[0].legs[0].duration_in_traffic.value<=data.routes[0].legs[0].duration.value*1.1)
												this.step='1';
											else if (data.routes[0].legs[0].duration_in_traffic.value<=data.routes[0].legs[0].duration.value*1.25)
												this.step='2';
											else
												this.step='3';
											this.havelocation=true;
										}
									},
									err => 
									{
										this.iserror=true;
										this.havelocation=true;
										console.log("There was an error, let's try again in 30 seconds");
									}
								  );	
				 
				 
				 
				}, (err) => {
								this.iserror=true;
								this.havelocation=true;
				});

}
 

itemSelected()
	{
		
			 var url = 'https://docs.google.com/viewer?url=' + encodeURIComponent("http://www.example.com/"+this.risk_pdf+".pdf");
			 let browser = new InAppBrowser(url,'_blank');
	}
Startnagivation()
	{
		if (this.platform.is('android'))
		{
				let options: LaunchNavigatorOptions = {
				launchMode: "turn-by-turn"
				};	
				LaunchNavigator.navigate([this.store_lat, this.store_lng],options);
		}
		else
		LaunchNavigator.navigate([this.store_lat, this.store_lng]);
	}
	
}

I suspect you are trying to interact with plugins before the platform is ready. Try wrapping all plugin interaction inside platform.ready().then(()).

Thank you for the suggestion. I used the wrong syntax in my question I wrote: page.ready() but I meant to write platform.ready().then(()) but same effect.

Could it be missing quotes around "!haveLocation"?

I will try, but I would expect if that is the case than it should never work, not only the first time. I’ll update as soon as I tried it out.

Nope same.

The even more interesting part is that the other ngIf conditions works on the first page load as well. Also in some cases I have a button which would open a pdf link using the inAppBrowser. If I press the button (aka I interact with the page), the ngif get’s updated correctly.

Ok, I got it figured it out. The problem was the same described here: http://stackoverflow.com/questions/34827334/triggering-angular2-change-detection-manually

I was updating the variables inside some async functions and no UI interaction took place so the binded elements did not get re-checked.
For those who are lazy to read the linked Stackoverflow and stumble across this thread here is the solution:

  1. You need to import { Component, NgZone } from '@angular/core';
  2. You need to included in your constructor like: constructor(private ref: ChangeDetectorRef)
  3. Every time you have an Async call which returns something which should updated the UI, call:
    this.ref.detectChanges(); this will force a full recheck of all elements and update accordingly.

Please see @rapropos answer below for the correct way.

I guess the problem is caused by the fact that I’m using the native SQL rather than the plugin SQLPlugin and the data gets updated from a callback of the SQL function which is not part of the typical Angular. Once I move the code to production I will be replacing the native SQL with the SQLPlugin and then this issue will be also resolved.

Anybody coming across this thread should consider manual change detection triggering an absolute last resort. If you are using typical Angular idioms of Observables and Promises, all this gets handled for you, resulting in much cleaner code.

The only time I’ve ever seen an unavoidable need for manual change detection is when attempting to modify select options for form controls inside a change handler for another form control, like in this case.