Fire Nativescript tabitem event when tabitem gets selected - angular2-nativescript

I am using a Nativescript (Angular 2) TabView with two TabItems. The XML is divided intro three files. One that holds the TabView and two others for each TabItem. Therefore I also have three TypeScript components.
At the moment I am loading data in the second TabItem's onInit method. The problem is that this action already happens when the first TabItem of the TabView is being displayed/loaded.
What is the best practice to load this data only when the second TabItem is selected?
This is my (shortened) code:
home.page.html:
<ActionBar title="Home"></ActionBar>
<TabView #tabview (selectedIndexChanged)="tabIndexChanged($event)" toggleNavButton>
<StackLayout *tabItem="{title: 'Tab 1'}">
<tab1></tab1>
</StackLayout>
<StackLayout *tabItem="{title: 'Tab 2'}">
<tab2></tab2>
</StackLayout>
</TabView>
home.page.ts:
import {Component} from "#angular/core";
#Component({
selector: "home-page",
templateUrl: "./pages/home/home.page.html",
providers: []
})
export class HomePage {
public activeTab: string;
public constructor() {
}
public tabIndexChanged(e: any) {
switch (e.newIndex) {
case 0:
console.log(`Selected tab index: ${e.newIndex}`);
break;
case 1:
console.log(`Selected tab index: ${e.newIndex}`);
break;
default:
break;
}
}
}
tab1.tab.html:
<StackLayout orientation="vertical" class="p-20">
<Label text="Tab 1"></Label>
</StackLayout>
tab1.tab.ts:
import { Component, OnInit } from "#angular/core";
#Component({
selector: "tab1",
templateUrl: "./pages/partials/tab1.tab.html",
providers: []
})
export class Tab1 implements OnInit {
public constructor() {}
public ngOnInit() {
console.log("init Tab 1");
}
}
tab2.tab.html:
<StackLayout orientation="vertical" class="p-20">
<Label text="Tab 2"></Label>
</StackLayout>
tab2.tab.ts:
import { Component, OnInit } from "#angular/core";
#Component({
selector: "tab2",
templateUrl: "./pages/partials/tab2.tab.html",
providers: []
})
export class Tab2 implements OnInit {
public constructor() {}
public ngOnInit() {
console.log("init Tab 2");
this.getSomeDataViaHttp();
}
private getSomeDataViaHttp() {
//getting data from an API
}
}
Is there an Angular 2 / Nativescript event other than onInit that would help here?
Or should I use the method tabIndexChanged in the home.page.ts for that?
Or put all the logic and the XML for the TabView back into one xml file and one ts file?
What is best practice?

You could use a service and a Subject as followed.
Import the service file in all ts files (use the name and location you like):
import { NavService } from "./services/nav.service";
Make sure to import it also in your app.module.ts to generally load it:
import { NavService } from "./services/nav.service";
#NgModule({
declarations: [
AppComponent,
],
bootstrap: [AppComponent],
imports: [
],
providers: [
NavService
]
})
export class AppModule {}
Create the service file in the specified location with the following content:
import { Injectable } from "#angular/core";
import { Subject } from "rxjs";
#Injectable()
export class NavService {
private currentState = new Subject<any>();
constructor () {
}
setCurrentState(navPoint: number){
this.currentState.next(navPoint);
}
getCurrentState() {
return this.currentState.asObservable();
}
}
Change the tab2.tab.ts to the following:
import { Component, OnInit } from "#angular/core";
import { NavService } from "./services/nav.service";
#Component({
selector: "tab2",
templateUrl: "./pages/partials/tab2.tab.html",
providers: []
})
export class Tab2 implements OnInit {
public constructor(private _navService: NavService) {}
public ngOnInit() {
console.log("init Tab 2");
this._navService.getCurrentState().subscribe(
(state) => {
if (state == {{something}}) {
//write your code here which should be executed when state has the property {{something}}
this.getSomeDataViaHttp();
}
}
);
}
private getSomeDataViaHttp() {
//getting data from an API
}
}
Call the setCurrentState of the service in your home.page.ts:
import {Component} from "#angular/core";
import { NavService } from "./services/nav.service";
#Component({
selector: "home-page",
templateUrl: "./pages/home/home.page.html",
providers: []
})
export class HomePage {
public activeTab: string;
public constructor(private _navService: NavService) {
}
public tabIndexChanged(e: any) {
this._navService.setCurrentState(e.newIndex);
}
}
Take care that the "typeof" setting and getting the state is correct.

Related

White Screen for Launch External App in Ionic 4

I have a page that would like to launch external app when a button is clicked and the function goToApp() should run.
Following is my code for on the ts file but everything on the page could be loaded until the point I added
import { AppLauncher, AppLauncherOptions } from '#ionic-native/app-launcher/ngx';
Which right after it the page doesn't load anymore. There is no error code returned. Any idea? Thanks in advance.
import { Component,OnInit,Input } from '#angular/core';
import { AppLauncher, AppLauncherOptions } from '#ionic-native/app-launcher/ngx';
import { ModalController, Platform } from '#ionic/angular';
import { DomSanitizer,SafeResourceUrl } from '#angular/platform-browser';
/*
Generated class for the Posts page.
See http://ionicframework.com/docs/v2/components/#navigation for more info on
Ionic pages and navigation.
*/
#Component({
selector: 'page-fsfastcheck',
templateUrl: 'fsfastcheck.html',
styleUrls: ['fsfastcheck.scss'],
})
export class FSFastCheckPage implements OnInit {
#Input()
url: string = "https://eastchenconsultancy.com/feng-shui-fast-check/";
url2: string = "https://eastchenconsultancy.com/appointment-list/";
urlSafe: SafeResourceUrl;
urlSafe2: SafeResourceUrl;
mySegment: string = 'travelrequest';
constructor(
public modalView: ModalController,
public sanitizer: DomSanitizer,
private appLauncher: AppLauncher, private platform: Platform) { }
ngOnInit() {
this.urlSafe= this.sanitizer.bypassSecurityTrustResourceUrl(this.url);
this.urlSafe2= this.sanitizer.bypassSecurityTrustResourceUrl(this.url2);
}
close() {
this.modalView.dismiss();
}
goToApp() {
const options: AppLauncherOptions = { }
if(this.platform.is('ios')) {
options.packageName = 'com.apple.compass'
} else {
options.packageName = 'com.gn.android.compass'
}
this.appLauncher.canLaunch(options)
.then((canLaunch: boolean) => console.log('Compass is available'))
.catch((error: any) => console.error('Compass is not available'));
}
}
Have you followed the standard part which you need to do when adding new modules to your app:
https://ionicframework.com/docs/native/overview#angular
Basically, you need to inject the module into the app:
// app.module.ts
import { AppLauncher } from '#ionic-native/app-launcher/ngx';
...
#NgModule({
...
providers: [
...
AppLauncher
...
]
...
})
export class AppModule { }

How to access provider members within a component in Ionic 4

I have a provider having a method getWeather() and I want to call it from home component. When I am trying to call getWeather() from home component I am getting error in console like: ERROR TypeError: Cannot read property 'getWeather' of undefined
weather.ts
import { Http } from '#angular/http';
import { Injectable } from '#angular/core';
import "rxjs/add/operator/map";
#Injectable()
export class WeatherProvider {
constructor(public http: Http) {
console.log('Hello WeatherProvider Provider: ');
}
getWeather() {
return this.http.get('https://samples.openweathermap.org/data/2.5/forecast?q=London,us&appid=b6907d289e10d714a6e88b30761fae22').map(res=>res.json());
}
}
home.ts
import { Component } from '#angular/core';
import { NavController } from 'ionic-angular';
import { WeatherProvider } from "../../providers/weather/weather";
//import { HttpModule } from "#angular/http";
#Component({
selector: 'page-home',
templateUrl: 'home.html'
})
export class HomePage {
weather:any;
constructor(public navCtrl: NavController, private wp:WeatherProvider) {
}
ionViewWillEnter() {
this.weather.getWeather().subscribe(weather=>{
console.log(weather)
})
}
}
The error is gone after restarting the ionic dev server. I don't know why the ionic is behaving like this.

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

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.

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.

How to create a custom form component by extending BaseInput in ionic2

I want to create a custom form input component in ionic2, by extending BaseInput. But it doesn't rendered, and I can't find it on the DOM.
import { Component, ElementRef, OnDestroy, Optional, Renderer,
ViewEncapsulation } from "#angular/core";
import { Config, Form, Item } from "ionic-angular";
import { BaseInput } from "ionic-angular/util/base-input";
import { NG_VALUE_ACCESSOR } from "#angular/forms";
#Component({
selector: 'my-checkbox',
template:
'<p>aaaaa</p>',
host: {
'[class.checkbox-disabled]': '_disabled'
},
providers: [ { provide: NG_VALUE_ACCESSOR, useExisting: MyCheckboxComponent, multi: true } ],
encapsulation: ViewEncapsulation.None,
})
export class MyCheckboxComponent extends BaseInput<any> implements OnDestroy {
constructor(form: Form, config: Config, elementRef: ElementRef, renderer: Renderer, #Optional() item: Item) {
super(config, elementRef, renderer, 'my-checkbox', [], form, item, null);
}
}
The code is copy from src/component/checkbox/checkbox.ts and make a little changes.
I had the same problem. My component did not get render within <ion-item> parent element. I fixed it by adding item-content directive
<ion-item>
<ion-label>Label</ion-label>
<my-checkbox item-content></my-checkbox>
</ion-item>