Ionic 3 NavController does not pop the view instead creates a new one - ionic-framework

The ionViewDidLoad function seem to get called twice, which is causing multiple views being created of AddressPage. I have debugged this and it looks like whenever data is updated the new instance of view gets created. This behaviour seems to happen only when I use fireabse to save the address. If I comment out the code to save the address new view is not created and app navigates to previous screen.
Any way to avoid this?
I have tried ViewCotnroller.dismiss() and NavController.pop() inside saveAddress method but non seem to avoid creation of new view.
#Component({
templateUrl: 'app.html'
})
export class MyApp {
rootPage:any = HomePage;
constructor(platform: Platform, statusBar: StatusBar) {
platform.ready().then(() => {
statusBar.styleDefault();
statusBar.backgroundColorByHexString('#1572b5');
});
}
}
Home Page
import {NavController } from 'ionic-angular';
#Component({
selector: 'page-home',
templateUrl: 'home.html'
})
export class HomePage {
constructor(public navCtrl: NavController, public firebaseProvider:
FirebaseProvider) {
}
//navigate to different view
navigate(){
this.navCtrl.push(AddressPage, {address:newAddress});
}
}
Address Page
import {NavController } from 'ionic-angular';
#Component({
selector: 'page-address',
templateUrl: 'address.html'
})
export class AddressPage {
constructor(public navCtrl: NavController, public firebaseProvider:
FirebaseProvider, private navParams: NavParams) {
this.addressKey = this.navParams.get('key');
}
ionViewDidEnter(){
//load some data from server
}
saveAddress(){
//save data to server
this.firebaseProvider.saveAddress(newAddress);
//move back
this.navCtrl.pop();
}
}
Firebase provider that uses AngularFireDatabase
import { Injectable } from '#angular/core';
import { AngularFireDatabase } from 'angularfire2/database';
#Injectable()
export class FirebaseProvider {
constructor(public afd: AngularFireDatabase) { }
saveAddress(address) {
this.afd.list('/addresses').push(address);
}
updateAddress(key,dataToUpdate){
return this.afd.list('addresses').update(key,dataToUpdate);
}
}
I have also tried this but it has the same issue.
this.firebaseProvider.saveAddress(newAddress).then(result => {
// loadingSpinner.dismiss();
this.navCtrl.pop();
});
this.firebaseProvider.updateAddress(this.addressKey, updateItems)
.then(() => {
// loadingSpinner.dismiss();
this.navCtrl.pop()
});
The HTML of save button
<button type="button" ion-button full color="primary-blue" (click)='saveAddress()'>Save</button>

Looks like unsubscribing to the subscribers fixes the issue. The HomePage view had subscribers which were not unsubscribed. I added the Observable Subscriptions into the array and unsubscribed as per code below.
ionViewWillLeave(){
this.subscriptions.forEach(item=>{
item.unsubscribe();
});
}

the push method returs a promise with the result of the action. I would change the save method like this:
saveAddress(address) {
return this.afd.list('/addresses').push(address);
}
Then in the controller I’d change it in this way:
saveAddress(){
//save data to serve
this.firebaseProvider.saveAddress(newAddress).then(result => {
//do yours validations
this.navCtrl.pop();
});
}
With thos you tide up the navigation of the page to the result of the Firebase execution. Give it a try to this approach and let me know if it didn’t work, anyway I would use oninit to load data only once as I guess you wanna do it rather than ionViewDidEnter.

Related

Displaying component property from record provider is undefined

I am creating an ionic app. In this modal, I want a select with options populated from my Provider (called recordProvider). categories should hold an array of objects from the recordProvider.
The name property of these objects is what goes in the select.
I am able to log categories immediately after it is assigned from recordsProvider and it shows all the proper records perfectly. However, the next line logs the length at 0. Most importantly, the UI errors with "Cannot read property 'name' of undefined"
Why does categories have this inconsistent value?
If it is just an issue of timing and categories will have the correct data in a moment, why isn't it updated in the UI? Isn't that the whole get with Angular?
How do I fix it?
Modal ts
import { Component } from '#angular/core';
import { IonicPage, NavController, NavParams,ViewController } from 'ionic- angular';
import { RecordsProvider } from './../../providers/records/records';
#IonicPage()
#Component({
selector: 'page-add-modal',
templateUrl: 'add-modal.html',
})
export class AddModalPage {
categories:object[] = [];
constructor(public navCtrl: NavController, public navParams: NavParams, public viewCtrl : ViewController, public recordProvider: RecordsProvider) {
}
ngOnInit() {
this.categories = this.recordProvider.getAllExpenseCategories();
console.log(this.categories);
console.log(this.categories.length);
}
public closeModal(){
this.viewCtrl.dismiss();
}
}
Modal HTML
<ion-content padding>
<h1 (click)="getCat()">Hello</h1>
<p>{{categories[0].name}}</p>
<ion-item>
<ion-label>categories</ion-label>
<ion-select>
<ion-option ng-repeat="obj of categories" value="{{obj.name}}">{{obj.name}}</ion-option>
</ion-select>
</ion-item>
</ion-content>
EDIT RecordsProvider
import { HttpClient } from '#angular/common/http';
import { Injectable } from '#angular/core';
import { Storage } from '#ionic/storage';
#Injectable()
export class RecordsProvider {
getAllExpenseCategories(){
let categories = [];
this.storage.forEach( (value, key, index)=>{
if(key.indexOf("Exp") == 0){
categories.push(value);
}
});
return categories;
}
}
Ionic Storage (localForage) uses async API, so I would make sure you write your methods with it accordingly, I would re-write the getAllExpenseCategories to leverage promise which is returned by storage:
getAllExpenseCategories(){
let categories = [];
this.storage.forEach( (value, key, index)=>{
if(key.indexOf("Exp") == 0){
categories.push(value);
}
}).then(()=>{
return categories;
})
}
In your case it seems like your method was returning empty array to the component, before storage completed its forEach cycle.
Let me know if this helped

Ionic spinner while Angularfire Firestore is loading

In an Ionic project i am using the code below to load a document collection from Firestore with the AngularFirestore wrappers.
Now that the content starts loading when the view was initialized i'm experiencing a delay by about 4-8 seconds until the firestore fetched data renders in my list-view, which is very very bad for the overall userexperience.
with the code below i'm able to show a loading spinner when the content starts loading bit i need it to stop showing the loader.
I have no clue how to trigger that event? Any help would be appreciated
thank you very much
import { City } from './../../model/City';
import { Component, AfterViewInit } from '#angular/core';
import { NavController, IonicPage, LoadingController } from 'ionic-angular';
import { AngularFirestore, AngularFirestoreCollection } from 'angularfire2/firestore';
import { Observable } from 'rxjs/Observable';
#IonicPage()
#Component({
selector: 'page-home',
templateUrl: 'home.html'
})
export class HomePage implements AfterViewInit {
citiesRef: AngularFirestoreCollection<City>
cities: Observable<City[]>;
loading = this.loadingCtrl.create({
content: 'Loading Regions...'
});
constructor(private loadingCtrl: LoadingController, private afs: AngularFirestore, public navCtrl: NavController) {
}
ngAfterViewInit(){
this.loading.present().then(()=>{
this.citiesRef = this.afs.collection<City>('regions', ref => ref.orderBy('name'));
this.cities = this.citiesRef.valueChanges();
})
}
}
Well, what I did was I subscribe to the this.cities. It worked in my case. The idea is it will fire loading.dismiss() once it is able to subscribe. Hope that helps
let loading = this.loadingCtrl.create({
content: 'Please wait...'
});
loading.present();
this.citiesRef=this.afs.collection('cities');
this.cities=this.citiesRef.valueChanges();
this.cities.subscribe(_=>{
loading.dismiss();
})

how to get realtime JSON from endpoint in Ionic App

Followed the content of the url to implement dynamic menu items using JSON file stored under /assets/data. The menu is working fine with stored JSON file. Now I need to dynamically retrieve the JSON of same format in real time from a Salesforce API and display its content.
Can someone please suggest what changes I need to make here? should the json path in getMainMenu() method be replaced with the actual Saleforce API?
Below is the data-service.ts
import { Injectable } from '#angular/core';
import { Http, Response } from '#angular/http';
import 'rxjs/add/operator/map';
/*
Generated class for the DataServiceProvider provider.
See https://angular.io/guide/dependency-injection for more info on providers
and Angular DI.
*/
#Injectable()
export class DataServiceProvider {
constructor(public http: Http) {
console.log('Hello DataServiceProvider Provider');
}
getMainMenu(){
return this.http.get('assets/data/mainmenu.json')
.map((response:Response)=>response.json().Categories);
}
}
and app.component.ts
import { Component, ViewChild } from '#angular/core';
import { Nav, Platform } from 'ionic-angular';
import { StatusBar } from '#ionic-native/status-bar';
import { SplashScreen } from '#ionic-native/splash-screen';
import { HomePage } from '../pages/home/home';
import { ListPage } from '../pages/list/list';
import { DataServiceProvider } from '../providers/data-service/data-service'
#Component({
templateUrl: 'app.html'
})
export class MyApp {
#ViewChild(Nav) nav: Nav;
rootPage: any = HomePage;
pages: any[]; //Array<{title: string, component: any}>;
mainmenu: any[];
constructor(public platform: Platform, public statusBar: StatusBar, public splashScreen: SplashScreen, public dataService: DataServiceProvider) {
this.initializeApp();
this.dataService.getMainMenu().subscribe((Response)=>{
this.mainmenu = Response;
console.log(this.mainmenu);
});
// used for an example of ngFor and navigation
this.pages = [
{ title: 'Home', component: HomePage },
{ title: 'List', component: ListPage }
];
}
initializeApp() {
this.platform.ready().then(() => {
// Okay, so the platform is ready and our plugins are available.
// Here you can do any higher level native things you might need.
this.statusBar.styleDefault();
this.splashScreen.hide();
});
}
openPage(page) {
// Reset the content nav to have just this page
// we wouldn't want the back button to show in this scenario
this.nav.setRoot(page.component);
}
toggleSection(i) {
this.mainmenu[i].open = !this.mainmenu[i].open;
};
toggleItem(i,j) {
this.mainmenu[i].SubCategories[j].open = !this.mainmenu[i].SubCategories[j].open;
};
}
It looks like you will need to update the url in the getMainMenu method to that of your api. There might be some other changes you will need to make, such as adding authentication headers, but if the data coming from the api is the same as whats stored in the assets folder, your component shouldn't care "where" the data comes from.

Splash screen make http request and pass the response to another page

What I am trying to do is when the splash screen is loading, a http request is made to the server to pull some information and pass the response to another page.
Below is the code I am working with.
import { Component } from '#angular/core';
import { Platform, LoadingController } from 'ionic-angular';
import { StatusBar, Splashscreen } from 'ionic-native';
import { CacheService } from "ionic-cache/ionic-cache";
import { Apis } from './apis';
import { StayPage} from '../pages/stay/stay';
#Component({
templateUrl: 'app.html',
providers: [Apis]
})
export class MyApp {
rootPage = StayPage;
constructor(platform: Platform, cache: CacheService, public loadingCtrl: LoadingController, public Apis: Apis ) {
cache.setDefaultTTL(60 * 60);
platform.ready().then(() => {
// Okay, so the platform is ready and our plugins are available.
// Here you can do any higher level native things you might need.
this.Apis.types().subscribe( response => {
response.results;
StatusBar.styleDefault();
Splashscreen.hide();
}, err => {
this.Apis.error( err );
});
});
}
}
When I run the above code, the splash screen is stuck on loading and doesn't move to another page.
You need to make HTTP request in constructor or ngOnInit of StayPage.
export class StayPage implements OnInit {
...
constructor(public navCtrl: NavController,
public navParams: NavParams
public http: Http) { }
ngOnInit(){
this.http.get(apiUrl)
.subscribe(
responseSuccess => ...
responseError => ...
}
}
}

force view to reload in ionic2 framework

After going through Clear History and Reload Page on Login/Logout Using Ionic Framework
I want to know same question, but for ionic2 using typescript.
On login and logout I need reload the app.ts, because there are classes that run libraries on construct.
it would be basically redirect to home and reload.
Found this answer here, (please note especially the line this.navCtrl.setRoot(this.navCtrl.getActive().component); which is by far the simplest solution that I've come across to reload present page for Ionic 2 & 3 and later versions of Angular (mine is 4), so credit due accordingly:
RELOAD CURRENT PAGE
import { Component } from '#angular/core';
import { NavController, ModalController} from 'ionic-angular';
#Component({
selector: 'page-example',
templateUrl: 'example.html'
})
export class ExamplePage {
public someVar: any;
constructor(public navCtrl: NavController, private modalCtrl: ModalController) {
}
refreshPage() {
this.navCtrl.setRoot(this.navCtrl.getActive().component);
}
}
If you want to RELOAD A DIFFERENT PAGE please use the following (note this.navCtrl.setRoot(HomePage);:
import { Component } from '#angular/core';
import { NavController, ModalController} from 'ionic-angular';
import { HomePage } from'../home/home';
#Component({
selector: 'page-example',
templateUrl: 'example.html'
})
export class ExamplePage {
public someVar: any;
constructor(public navCtrl: NavController, private modalCtrl: ModalController) {
}
directToNewPage() {
this.navCtrl.setRoot(HomePage);
}
}
Ionic 1
I haven't used Ionic 2 but currently i m using Ionic 1.2 and if they are still using ui-router than you can use reload: true in ui-sref
or you can add below code to your logout controller
$state.transitionTo($state.current, $stateParams, {
reload: true,
inherit: false,
notify: true
});
Angular 2
Use
$window.location.reload();
or
location.reload();
You have to implement the CanReuse interface, and override the routerCanReuse to return false. Then, try calling router.renavigate().
Your component should look like this:
class MyComponent implements CanReuse {
// your code here...
routerCanReuse(next: ComponentInstruction, prev: ComponentInstruction) {
return false;
}
}
And then, when you perform login/logout, call:
// navigate to home
router.renavigate()
This is a hack, but it works.
Wrap the logic that follows your template adjustment in a setTimeout and that gives the browser a moment to do the refresh:
/* my code which adjusts the ng 2 html template in some way */
setTimeout(function() {
/* processing which follows the template change */
}, 100);
For ionic 2 it works for me when you force page reload by triggering fireWillEnter on a view controller
viewController.fireWillEnter();
Here is what worked for me to refresh only current page-
I am trying to call refreshMe function when I call onDelete from my view page,
See how my page.ts file looks-
export class MyPage {
lines of code goes here like
public arr1: any;
public arr2: any;
public constructor(private nav: NavController, navParams: NavParams) {
this.nav = nav;
this.arr1 = [];
this.arr2 = [];
// console.log("hey array");
}
onDelete() {
perform this set of tasks...
...
...
refreshMe()
}
refreshMe() {
this.nav.setRoot(MyPage);
}
}
This is just refreshing only current page.
We can also call this function from view if we need as--
<ion-col width-60 offset-30 (click)="refreshMe()">
....
....
</ion-col>
I personally use these three lines to totally refresh a component
let active = this.navCtrl.getActive(); // or getByIndex(int) if you know it
this.navCtrl.remove(active.index);
this.navCtrl.push(active.component);
You can use the ionViewWillLeave() to display your splashscreen while component is reloading and then hide it with ionViewDidEnter() once its loaded.
Hope it helps