I start my project with tabs template and then I add side menu into it. Both tabs and side menu work, but if i click on menu item, page lost tabs view.
app.components.ts
#Component({
templateUrl: 'app.html'
})
export class MyApp {
#ViewChild(Nav) nav: Nav;
rootPage = TabsPage;
pages: Array<{title: string, component: any}>;
constructor(public platform: Platform) {
this.initializeApp();
// used for an example of ngFor and navigation
this.pages = [
{ title: 'Home', component: HomePage },
{ title: 'About', component: AboutPage },
{ title: 'Contact', component: ContactPage }
];
}
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.
StatusBar.styleDefault();
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);
}
app.html
<ion-menu [content]="content" push persistent=true>
<ion-header>
<ion-toolbar>
<ion-title>Menu</ion-title>
</ion-toolbar>
</ion-header>
<ion-content class="outer-content">
<ion-list>
<button menuClose ion-item *ngFor="let p of pages" (click)="openPage(p)">
{{p.title}}
</button>
</ion-list>
</ion-content>
</ion-menu>
<ion-nav [root]="rootPage" #content swipeBackEnabled="false"></ion-nav>
The issue is you are setting the component selected from the menu as root.
this.nav.setRoot(page.component);
This replaces the existing TabsPage with the selected page as root rather than change the selected tab of the TabsPage.
A solution is to send the page object from openPage() function to TabsPage using Events API or a service with Observable/Subject.
openPage(page) {
this.events.publish('menu selected',page);
}
In your TabsPage,
ngOnInit(){
this.events.subscribe('menu selected',this.openPage.bind(this))
}
openPage(page){
this.tabs.select(page.index);//set an additional property with index same as tab order.
}
Related
(Disclaimer: I am still very new to ionic and coding in general, if any terminology below is wrong please let me know as part of my learning process. Thanks in advance for help)
Problem: Sidemenu show after is pressed, but the menu cannot be clicked
Background: I have an ionic project setup with side menu in split pane. It was structured with a separate ionic page for the menu, route directly from root page to lazy-load the menu page, then from there the menu routing module handle the child route to display different pages.
I have these setup up by following through this youtube tutorial https://www.youtube.com/watch?v=I82_roQSgco
Long story short, after building up my app and now wanting to setup route guard, I personally found it makes no sense to have an empty app component and route to a menu as an entry point. So I want to move the menu and the routing logic back into the app component and app routing.
Everything seems fine with the migration, except for the side-menu. the side-menu will show if I press the menu toggle button, but the buttons in the menu are unclickable as if there is another element overlay on top of it. However, if I use a full width browser, where the menu is not hidden in the first place, it works just fine.
I simply moved everything in the html template from the menu page into that of the app component html template within the tab.
I spent hours troubleshooting with reference to the default side-menu app and my previously working strategy, and I still cannot figure out why I cannot click the menu.
code attached below, any help appreciated
app-routing.module.ts
import { NgModule } from '#angular/core';
import { PreloadAllModules, RouterModule, Routes } from '#angular/router';
import { AngularFireAuthGuard, redirectUnauthorizedTo } from "#angular/fire/auth-guard";
import { AppComponent } from './app.component';
const redirectToLogin = () => redirectUnauthorizedTo(['login']);
const routes: Routes = [
{
//authguard to be implement in the root path
path: '',
component:AppComponent,
children:[
{
path: 'users',
loadChildren: './pages/users/users.module#UsersPageModule'
},
{
path: 'students',
loadChildren: './pages/students/students.module#StudentsPageModule'
},
{
path: '',
loadChildren: './pages/home/home.module#HomePageModule'
},
]
},
{
//redirect to login page if not logged in
path: 'login',
loadChildren: './pages/login/login.module#LoginPageModule'
}
];
#NgModule({
imports: [
RouterModule.forRoot(routes, { preloadingStrategy: PreloadAllModules })
],
exports: [RouterModule]
})
export class AppRoutingModule { }
app.component.html
<ion-app>
<ion-split-pane contentId ="content">
<ion-menu contentId="content">
<ion-header>
<ion-toolbar>
<ion-title>Menu</ion-title>
</ion-toolbar>
</ion-header>
<ion-content>
<ion-list>
<ion-menu-toggle auto-hide="false" *ngFor = "let p of pages">
<ion-item [routerLink]="p.url" routerDirection="root" [class.active-item]="selectedPath === p.url">
{{p.title}}
</ion-item>
</ion-menu-toggle>
</ion-list>
</ion-content>
</ion-menu>
<ion-router-outlet id="content" main></ion-router-outlet>
</ion-split-pane>
</ion-app>
app.component.ts
import { Component, OnInit } from '#angular/core';
import { Platform } from '#ionic/angular';
import { SplashScreen } from '#ionic-native/splash-screen/ngx';
import { StatusBar } from '#ionic-native/status-bar/ngx';
import { Router, RouterEvent } from '#angular/router';
#Component({
selector: 'app-root',
templateUrl: 'app.component.html',
styleUrls: ['app.component.scss']
})
export class AppComponent implements OnInit{
constructor(
private platform: Platform,
private splashScreen: SplashScreen,
private statusBar: StatusBar,
private router:Router,
) {
this.initializeApp();
}
initializeApp() {
this.platform.ready().then(() => {
this.statusBar.styleDefault();
this.splashScreen.hide();
});
}
pages = [
{
title:'Home',
url:''
},
{
title:'Students',
url:'students'
},
{
title:'Users',
url:'users'
}
]
selectedPath = '';
ngOnInit(){
this.router.events.subscribe((event:RouterEvent) =>{
this.selectedPath = event.url;
});
}
}
home.page.html (an empty home page with the ion-menu-button)
<ion-header>
<ion-toolbar>
<ion-buttons slot="start">
<ion-menu-button></ion-menu-button>
</ion-buttons>
<ion-title>Home</ion-title>
</ion-toolbar>
</ion-header>
<ion-content>
</ion-content>
You are forcing your router-outlet to use App-component as a component, when App-component is loaded by default. Easy solution is to change your routes const
const routes: Routes = [
{ path: '', redirectTo: 'home', pathMatch: 'full' },
{
path: 'users',
loadChildren: './pages/users/users.module#UsersPageModule'
},
{
path: 'students',
loadChildren: './pages/students/students.module#StudentsPageModule'
},
{
path: '',
loadChildren: './pages/home/home.module#HomePageModule'
},
];
for sub component inside router i suggest you to watch this video: https://youtu.be/hkaa2mnqOI8
I'm having problems with clicking the button upon taking a test.
Scenario:
Upon Logging in -> the user takes a test and submits -> the user is redirected on the home page. But on my home page i cant click on the "Menu button"
on my Login.ts
if (this.checker == "false" || this.checker == null) {
this.navCtrl.setRoot(LearnertestPage);
} else {
this.navCtrl.setRoot(SplashscreenPage);
}
on my test.ts
in the alert controller, I have this
{
text: 'Yes',
handler: data => {
this.learningStyles.push(
[
{style: "Auditory", value: AudioTotal},
{style: "Logical", value: LogicalTotal},
{style: "Physical", value: PhysicalTotal},
{style: "Social", value: SocialTotal},
{style: "Solitary", value: SolitaryTotal},
{style: "Visual", value: VisualTotal},
{style: "Verbal", value: VerbalTotal}]
);
this.userChecker.update( this.currentUser, { Checker: 'true' });
this.navCtrl.setRoot(SplashscreenPage);
}
}
And lastly on my Splash screen or home :
HTMl:
<ion-menu [content]="content">
<ion-content>
<ion-item style="background-color:#00aced">
<img src="./assets/img/adapt.png" height="100px" width="350px"/>
</ion-item>
<ion-list>
<button ion-item *ngFor="let p of pages" (click)="openPage(p)">
<ion-icon name="{{p.icon}}"></ion-icon> {{p.title}}
</button>
<button ion-item (click)="doConfirm()">
<ion-icon name="log-out"></ion-icon> Logout
</button>
</ion-list>
on the splashscreen.ts
#ViewChild(Nav) nav: Nav;
selectedItem: any;
rootPage: any = ListPage;
selectedTheme:String;
icons: string[];
pages: Array<{ title: string, component: any, icon: string }>
constructor(){
// used for an example of ngFor and navigation
this.pages = [
{ title: 'Home', component: SplashscreenPage, icon: this.icons[0], },
{ title: 'Earth Science', component: LessonPage, icon: this.icons[1] },
{ title: 'Progress', component: ProfilePage, icon: this.icons[2] }
];
}
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); }
I can't seem to click on this button. Hmm. What am I doing wrong?
try to custom toggle the menu. add the toggleMenu method to the (click) of the menu button
import { Component } from '#angular/core';
import { MenuController } from 'ionic-angular';
#Component({...})
export class MyPage {
constructor(public menuCtrl: MenuController) {
}
toggleMenu() {
this.menuCtrl.toggle();
}
}
I want to have an sidebar that can display the name of the user who logged in. I have an account page that display all the details of the user. This is the code of the typescript below for the accounts page.
import { Component } from '#angular/core';
import { NavController, AlertController} from 'ionic-angular';
import {Headers} from "#angular/http";
import 'rxjs/add/operator/map';
import {Storage} from "#ionic/storage";
import { Global } from '../../providers/global';
#Component({
selector: 'page-account',
templateUrl: 'account.html',
})
export class AccountPage {
constructor(public navCtrl: NavController,
private storage: Storage,
public alertCtrl: AlertController,
public global: Global
)
{
//
}
public ACCOUNT_URL = this.global.url + "/api/inspectors";
credentials:any;
contentHeader = new Headers({"Content-Type": "application/json"});
error: any;
user: any;
token_type: any;
access_token: any;
refresh_token:any;
ionViewDidLoad() {
console.log('ionViewDidLoad AccountPage');
this.getAccessToken();
this.getAccount();
}
getAccessToken(){
this.storage.get('access_token').then((value) => {
this.access_token=value;
});
}
getAccount(){
this.storage.get('user').then((value) => {
this.user=value;
});
}
}
and this is the code in my html.
<ion-header>
<ion-navbar color="danger">
<button ion-button menuToggle>
<ion-icon name="menu"></ion-icon>
</button>
<ion-title>Account</ion-title>
</ion-navbar>
</ion-header>
<ion-content class="page-account">
<ion-card>
<ion-item *ngIf="user">
<ion-card-content text-wrap>
<h2>Name: {{user.name}}</h2>
<p>{{user.cellphone_no}}</p>
<p> Address: {{user.address}} </p>
<p> Email: {{user.email}} </p>
</ion-card-content>
</ion-item>
</ion-card>
</ion-content>
What I want to happen is to delete the account pages and put the details of the user in top of the sidebar. So I have to put it into the app.html or app.ts but how can I define the property in my root component in order to display the details of that user.
Here is the code below in my 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 {Storage} from "#ionic/storage";
import { LoginPage } from '../pages/login/login';
import { AccountPage } from '../pages/account/account';
import { InspectionPage } from '../pages/inspection/inspection';
#Component({
templateUrl: 'app.html'
})
export class MyApp {
rootPage:any = LoginPage;
#ViewChild(Nav) nav: Nav;
pages: Array<{title: string, component: any, icon: string, color: string}>;
constructor(platform: Platform,
statusBar: StatusBar,
private storage: Storage,
splashScreen: SplashScreen) {
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.
statusBar.styleDefault();
splashScreen.hide();
});
// used for an example of ngFor and navigation
this.pages = [
//{ title: 'Home', component: HomePage, icon: 'home', color: 'primary' },
{ title: 'Home', component: InspectionPage, icon: 'home', color: 'danger' },
{ title: 'Account', component: AccountPage, icon: 'person', color: 'primary' }
];
}
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);
}
logout() {
// Reset the content nav to have just this page
// we wouldn't want the back button to show in this scenario
this.storage.remove('access_token');
this.storage.remove('username');
this.storage.remove('data');
this.storage.remove('user');
this.nav.setRoot(LoginPage);
}
}
and here is the code in my sidemenu or html.
<ion-menu [content]="content" id="myMenu">
<ion-header>
<ion-toolbar color="danger">
<ion-title>Menu</ion-title>
</ion-toolbar>
</ion-header>
<ion-content>
<ion-list>
<button menuClose ion-item *ngFor="let p of pages" (click)="openPage(p)">
<ion-icon [name]="p.icon" [color]="p.color" item-left></ion-icon> {{p.title}}
</button>
<button menuClose ion-item (click)="logout()">
<ion-icon name="log-out" color="default" item-left></ion-icon> Logout
</button>
</ion-list>
</ion-content>
</ion-menu>
<!-- Disable swipe-to-go-back because it's poor UX to combine STGB with side menus -->
<ion-nav [root]="rootPage" #content swipeBackEnabled="false"></ion-nav>
What I want to happen is to put the details of the user in the sidebar like what I did in the account page. Sorry for my long question. I tried searching for it but can't find any answer to it.
Looking for help.
Thanks in advance.
I would recommend that you create a service to handle the state of the user login. That way the state is handled in a central place and it will be much easier to maintain.
One possible approach would be to use a BehaviourSubject inside your service, which you can then subscribe to on every page that you need your user object (like your account page and your app component).
import {Injectable} from '#angular/core'
import {BehaviorSubject} from 'rxjs/BehaviorSubject';
#Injectable()
export class UserService {
// Observable user object (replace any with your user class/interface)
private _userObject = new BehaviorSubject<any>({});
// Expose an observable that can be used by components
userObject$ = this._userObject.asObservable();
// Method to update the user
changeUser(user) {
this._userObject.next(user);
}
}
You can now use the service like that (you have to implement the subscription logic in every component where you want to have access to your user):
import {Component} from '#angular/core';
import {UserService} from './user.service';
#Component({
selector: 'account-page'
})
export class AccountPage {
user: any;
subscription: Subscription;
constructor(private _userService: UserService) {}
ngOnInit() {
this.subscription = this._userService.userObject$
.subscribe(item => this.user = item);
}
ngOnDestroy() {
// prevent memory leak when component is destroyed
this.subscription.unsubscribe();
}
login() {
this._userService.changeUser({
name: 'Name' // Replace with name / user object
});
}
logout() {
this._userService.changeUser({});
}
}
As mentioned above, the big advantage is maintainability. If you ever change your user object, it only requires minimal changes, whereas the solution by #Tomislav Stankovic requires changes in every component where the user is used.
In your login page, when user is successfully logged-in, store data to localStorage
login(username,password){
this._api.userLogin().subscribe(res => {
if(res.status == 'ok'){
localStorage.setItem('user_first_name', res.user_first_name);
localStorage.setItem('user_last_name', res.user_last_name);
}
}
And then in app.component.ts get data from localStorage
this.first_name = localStorage.getItem('user_first_name');
this.last_name = localStorage.getItem('user_last_name');
Display data in app.html
<p>{{first_name}}</p>
<p>{{last_name}}</p>
On log-out clear localStorage
logout(){
localStorage.clear();
}
I want to show a list of the bluetooth devices present around in Ionic2.
I made .ts and .html page in Ionic.
Now I can turn on bluetooth on my phone using the app, but can't see other devices.
.ts file:
import { Component } from '#angular/core';
import { Platform, AlertController } from 'ionic-angular';
import { BluetoothSerial } from 'ionic-native';
import { NavController } from 'ionic-angular';
#Component({
selector: 'page-page1',
templateUrl: 'page1.html'
})
export class Page1 {
public working:string;
public var2: string ;
public lists = [];
constructor(private alertCtrl: AlertController, public platform: Platform, public navCtrl: NavController) {
platform.ready().then(() => {
BluetoothSerial.isConnected()
.then(() => {
console.log('is connected');
}, (err) => {
console.log(' not connected')
})
});
}
enableBluetooth(){
BluetoothSerial.enable()
}
discoverBluetooth(){
/* BluetoothSerial.setDeviceDiscoveredListener(function(device) {
console.log('Found: '+device.id);
});*/
}
unpairedBluetooth(){
BluetoothSerial.discoverUnpaired().then(function(devices) {
devices.forEach(function(device) {
console.log(device.id)
});
})
}
listDevices(){
BluetoothSerial.isEnabled().then((data)=> {
console.log(data);
BluetoothSerial.list().then((allDevices) => {
this.lists = allDevices;
let result = [];
for (var out in this.lists) {
result.push(out);
}
})
})
}}
.html file:
<ion-header>
<ion-navbar>
<button ion-button menuToggle>
<ion-icon name="menu"></ion-icon>
</button>
<ion-title>Page One</ion-title>
</ion-navbar>
</ion-header>
<ion-content padding>
<ion-buttons>
<button ion-button (click) = "enableBluetooth()">Turn on bluetooth</button>
</ion-buttons>
<ion-buttons>
<button ion-button (click) = "listDevices()">List devices</button>
</ion-buttons>
<ion-list>
<ion-item *ngFor="let out of lists">{{out}}</ion-item>
</ion-list>
<ion-buttons>
<button ion-button (click) = "unpairedBluetooth()">Unpair</button>
</ion-buttons>
</ion-content>
I have response in my application but it only shows:
[object Object]
How can I read these?
i think in this line :
<ion-item *ngFor="let out of lists">{{out}}</ion-item>
change {{out}} to something like {{out.name}} , out is complex object but you should display key of object. if you want where name is come check this Bluetooth Serial
[{
"class": 276,
"id": "10:BF:48:CB:00:00",
"address": "10:BF:48:CB:00:00",
"name": "Nexus 7"
}]
My tabs.ts (simpilified) - data used to generated tabs with *ngFor is brought from php backend:
import ...
export interface Group {
id: number;
group: string;
};
#Component( {
template: `
<ion-tabs #myTabs selectedIndex="0">
<ion-tab *ngFor="let tab of userGroups" [root]="page" [rootParams]="tab.id" [tabTitle]="tab.group" tabIcon="pulse"></ion-tab>
</ion-tabs>
`
})
export class GroupsTabsPage {
userGroups: Group[];
page: any = TabStudentsPage;
constructor( public app: App, public api: Api, public navParams: NavParams ) {
this.api.getGroupsList()
.subscribe(
data => {
this.userGroups = data;
},
err => {
this.app.getRootNav().push( LoginPage )
}
);
// ionViewDidEnter() {
// }
}
}
The result is invisible tabs. But when you hover your mouse ovet them, the cursor changes into 'hand' and you can click them. When clicked, the whole tabs bar becomes visible and all works as expected.
When I used #ViewChild to refer to the tabs elements, the interesting thing is that its 'length' property is always 0 (I checked in ionViewDidLoad event). Trying to select one of the tabs programatically also fails - they are like ghosts;)
Also when you place at least one static tab next to *ngFor ones in the template, all *ngFor ones show up but the static is always selected no matter what you select programatically or in selectedIndex property on tabs element.
Any idea guys? I've wasted three days..
that's a known bug, take a look at the element css, the subview's .tabbar has opacity of 0. I've just fixed it with a an override of opacity: 1. Ugly, but works...
Creating ion-tab from observable (dynamically) has some bugs (duplicates, wrong rendering etc) I use a workaround to avoid it, it consist of removing and loading the ion-tabs runtime every time then observable changes.
Parent template:
<div #pluginTabContainer></div>
Parent component:
#ViewChild("pluginTabContainer", {read: ViewContainerRef}) pluginTabContainer:ViewContainerRef;
...
plugins$.subscribe((pluginTabs:Array<PluginTabType>) => { let componentFactory = this.componentFactoryResolver.resolveComponentFactory(PluginTabContainerComponent); this.pluginTabContainer.clear(); this.pluginTabContainertRef = this.pluginTabContainer.createComponent(componentFactory); this.pluginTabContainertRef.instance.data = pluginTabs;
...
ngOnDestroy() { this.pluginTabContainertRef.destroy(); }
Loaded ion-tabs template:
<ion-tabs> <ion-tab *ngFor="let tab of data" [root]="'PluginTabPage'" [rootParams]="tab"></ion-tab> </ion-tabs>
Loaded ion-tabs component (getting parameter):
#Input() data: PluginTabType;
Hope will be helpful for you.
I had a similar issue during development and I was able to solve this by making ngOninit async and calling a timeout to set the selected tab.
view
<ion-tabs #ctrlPanelTabs class="tabs-basic">
<ion-tab *ngFor="let appTab of appTabs" tabTitle={{appTab.name}} [root]="rootPage"></ion-tab>
</ion-tabs>
1) ngOninit is async
2) this.ctrlPanelTabs.select(0); is set inside a timeout function
import { Component, OnInit, ViewChild } from '#angular/core';
import { NavController, Tabs } from 'ionic-angular';
import { AppSettings } from '../../common/app.config';
import { AppTab } from '../../models/app-tab';
import { AppTabService } from '../../services/app-tab.service';
import { PanelTabComponent } from './panel-tab';
#Component({
selector: 'page-control-panel',
templateUrl: 'control-panel.html',
providers: [AppTabService]
})
export class ControlPanelPage implements OnInit {
#ViewChild("ctrlPanelTabs") ctrlPanelTabs: Tabs;
appTabs: AppTab[] = [];
message: string;
rootPage = PanelTabComponent;
constructor(public navCtrl: NavController,
private appTabService: AppTabService) {
console.log("Control Panel Page : Constructor called..");
}
async ngOnInit() {
console.log("Control Panel Page : Entering ngOninit..");
await this.loadAppTabs();
setTimeout(() => {
this.ctrlPanelTabs.select(0);
}, 100);
console.log("Control Panel Page : Exiting ngOninit..");
}
async loadAppTabs() {
console.log("Control Panel Page : Entering loadAppTabs..");
await this.appTabService.getAppTabsHierarchyBySlaveDeviceId(AppSettings.selSlaveDeviceId)
.then((response: any) => {
this.appTabs = JSON.parse(response.result);
console.log(this.appTabs);
console.log("Control Panel Page : Exiting loadAppTabs..");
});
}
}