Ionic4 - ion-select does not correctly update after dynamically added option - ionic-framework

The issue is that ion-select multi selection drop-down does not update (selection and as a consequence its control name) after dynamically updating underlying values list.
I have tried triggering change detection manually to pickup the changes but doesn't have an effect.
The following is the minimalistic snippet that illustrates the issue:
#Component({
selector: 'app-root',
template: `
<ion-button color="primary" (click)="AddItem()">Add Item</ion-button>
<ion-item>
<ion-label>Broken dropdown</ion-label>
<ion-select
multiple="true"
placeholder="Broken dropdown"
[compareWith]="compareWith"
>
<ion-select-option *ngFor="let item of stuff" [value]="item" [selected]="item.selected">
{{item.name}}
</ion-select-option>
</ion-select>
</ion-item>
`
})
export class AppComponent {
public stuff = [{ name: 'item1', selected: false }, { name: 'item2', selected: true }];
constructor(private platform: Platform, private splashScreen: SplashScreen, private statusBar: StatusBar) {
this.initializeApp();
}
initializeApp() {
this.platform.ready().then(() => {
this.statusBar.styleDefault();
this.splashScreen.hide();
});
}
AddItem() {
this.stuff.push({ name: 'item3', selected: true });
}
compareWith(e1, e2) {
return e1 && e2 ? e1.name === e2.name : e1 === e2;
}
}
After clicking "Add Item" button I would expect the control name to be updated as well as after opening the drop-down the item2 and item3 would be selected.
I did workaround the issue by manually manipulating control but I would like to understand where the issue is that it doesn't update itself out of the box.
Thanks!

Related

ionic 2/3: How to select tabs dynamically

I created a sidemenu app in Ionic 2 which contains a Main Tab and 3 sub tab pages.
It looks like this:
This is the code for Main tabs page:
<ion-header>
<ion-navbar #content color="black">
<button ion-button menuToggle>
<ion-icon name="menu"></ion-icon>
</button>
<ion-title >Main Tab</ion-title>
</ion-navbar>
</ion-header>
<ion-tabs [selectedIndex]="mySelectedIndex" #myTabs>
<ion-tab [root]="tabARoot" tabTitle="Tab A" tabIcon="information-circle"></ion-tab>
<ion-tab [root]="tabBRoot" tabTitle="Tab B" tabIcon="information-circle"></ion-tab>
<ion-tab [root]="tabCRoot" tabTitle="Tab C" tabIcon="information-circle"></ion-tab>
</ion-tabs>
It contains 3 sub tab pages with some gibberish on it.
This is how my side menu looks like.
So when a user clicks on Tab B link from side menu, he should navigate to main tabs page with Tab B as selected. But now when I click, Tab A is selected by default.
Is it possible to change this behavior?
My app.component.ts file looks like this
import { Component, ViewChild } from '#angular/core';
import { Nav, Platform, App, Tabs } from 'ionic-angular';
import { StatusBar } from '#ionic-native/status-bar';
import { SplashScreen } from '#ionic-native/splash-screen';
import { MainPage } from '../pages/main/main';
#Component({
templateUrl: 'app.html'
})
export class MyApp {
#ViewChild(Nav) nav: Nav;
rootPage: any = MainPage;
pages: Array<{title: string, component: any}>;
constructor(public platform: Platform, public statusBar: StatusBar, public splashScreen: SplashScreen, private app: App) {
this.initializeApp();
// used for an example of ngFor and navigation
this.pages = [
{ title: 'Tab A', component: MainPage },
{ title: 'Tab B', component: MainPage },
{ title: 'Tab C', component: MainPage },
];
}
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);
}
}
From somewhere I got a solution which didn't work, obviously.
in that solution, it was mentioned to do it like given below but it didn't work'
this.nav.setRoot(page.component, {tabIndex: 2});
There is a property called selectedIndex in ion-tabs component to set the default tab. Since you are passing tabIndex while clicking the main tab you can do something like this
In the controller
selectedTabIndex = navParams.get('tabIndex');
In the view
<ion-tabs [selectedIndex]="selectedTabIndex">
<ion-tab [root]="tabA"></ion-tab>
<ion-tab [root]="tabB"></ion-tab>
<ion-tab [root]="tabC"></ion-tab>
</ion-tabs>
Else if you want to select any tabs programatically from controller you can do this, first get the reference of your tabs and then you can use the select() function to set the selected tab you want by passing the index
#ViewChild('myTabs') tabRef: Tabs;
ionViewDidLoad() {
this.tabRef.select(1, { animate: false });
}
#john Doe
set root page = 'MenuPage' page
rootPage = 'MenuPage'
try below code:
`
src/pages/menu/menu.html :
<ion-menu [content]="content">
<ion-header>
<ion-toolbar>
<ion-title>Menu</ion-title>
</ion-toolbar>
</ion-header>
<ion-content>
<ion-list>
<button ion-item menuClose *ngFor="let p of pages" (click)="openPage(p)">
<ion-icon item-start [name]="p.icon" [color]="isActive(p)"></ion-icon>
{{ p.title }}
</button>
</ion-list>
</ion-content>
</ion-menu>
<!-- main navigation -->
<ion-nav [root]="rootPage" #content swipeBackEnabled="false"></ion-nav>
src/pages/menu/menu.ts:
import { Tab2Page } from './../tab2/tab2';
import { Tab1Page } from './../tab1/tab1';
import { TabsPage } from './../tabs/tabs';
import { Component, ViewChild } from '#angular/core';
import { IonicPage, NavController, Nav } from 'ionic-angular';
export interface PageInterface {
title: string;
pageName: string;
tabComponent?: any;
index?: number;
icon: string;
}
#IonicPage()
#Component({
selector: 'page-menu',
templateUrl: 'menu.html',
})
export class MenuPage {
// Basic root for our content view
rootPage = 'TabsPage';
// Reference to the app's root nav
#ViewChild(Nav) nav: Nav;
pages: PageInterface[] = [
{ title: 'Tab 1', pageName: 'TabsPage', tabComponent: 'Tab1Page', index: 0, icon: 'home' },
{ title: 'Tab 2', pageName: 'TabsPage', tabComponent: 'Tab2Page', index: 1, icon: 'contacts' },
{ title: 'Special', pageName: 'SpecialPage', icon: 'shuffle' },
];
constructor(public navCtrl: NavController) { }
openPage(page: PageInterface) {
let params = {};
// The index is equal to the order of our tabs inside tabs.ts
if (page.index) {
params = { tabIndex: page.index };
}
// The active child nav is our Tabs Navigation
if (this.nav.getActiveChildNav() && page.index != undefined) {
this.nav.getActiveChildNav().select(page.index);
} else {
// Tabs are not active, so reset the root page
// In this case: moving to or from SpecialPage
this.nav.setRoot(page.pageName, params);
}
}
isActive(page: PageInterface) {
// Again the Tabs Navigation
let childNav = this.nav.getActiveChildNav();
if (childNav) {
if (childNav.getSelected() && childNav.getSelected().root === page.tabComponent) {
return 'primary';
}
return;
}
// Fallback needed when there is no active childnav (tabs not active)
if (this.nav.getActive() && this.nav.getActive().name === page.pageName) {
return 'primary';
}
return;
}
}
src/pages/tabs/tabs.html
<ion-tabs [selectedIndex]="myIndex">
<ion-tab [root]="tab1Root" tabTitle="Tab 1" tabIcon="home"></ion-tab>
<ion-tab [root]="tab2Root" tabTitle="Tab 2" tabIcon="contacts"></ion-tab>
</ion-tabs>
src/pages/tabs/tabs.ts
import { Component } from '#angular/core';
import { IonicPage, NavController, NavParams } from 'ionic-angular';
#IonicPage()
#Component({
selector: 'page-tabs',
templateUrl: 'tabs.html',
})
export class TabsPage {
tab1Root: any = 'Tab1Page';
tab2Root: any = 'Tab2Page';
myIndex: number;
constructor(navParams: NavParams) {
// Set the active tab based on the passed index from menu.ts
this.myIndex = navParams.data.tabIndex || 0;
}
}
thanks

This.username is undefined in ionViewDidLoad() but defined in ionViewWillLeave() - Ionic3 chat mobile application

I'm trying to create a simple chat application with Ionic 3 and Firebase. Registering, logging in users, sending and displaying their messages work. This is a common chat room for all users.
I'd like a message to appear in the chat room when a user is logged in or logged out to let other users know. When test user is logged in, this message appears: "has joined the room"
When test user is logged out, this message appears: "test#gmail.com has left the room"
I'd like the username (email address) to show when the user is logged in as well. I'd like this message to appear: "test#gmail.com has joined the room"
I tried write this.username on the console, but it only writes this to the console: ionViewDidLoad ChatPage. The username doesn't appear on the console:
console.log('ionViewDidLoad ChatPage', this.username);
chat.ts:
import { Component } from '#angular/core';
import { IonicPage, NavController, NavParams } from 'ionic-angular';
import { AngularFireDatabase, AngularFireObject } from 'angularfire2/database';
import { Storage } from '#ionic/storage';
import { Subscription } from 'rxjs/Subscription';
import $ from 'jquery';
#IonicPage()
#Component({
selector: 'page-chat',
templateUrl: 'chat.html',
})
export class ChatPage {
username: string= '';
message: string= '';
obsRef: AngularFireObject<any>;
obsToData: Subscription;
messages: object[]= [];
constructor(public db: AngularFireDatabase, public navCtrl: NavController, public navParams: NavParams, private storage: Storage) {
this.storage.get('username').then((val) => {
if (val != null) {
this.username= val;
}
});
this.obsRef = this.db.object('/chat');
this.obsToData = this.obsRef.valueChanges().subscribe( data => {
var data_array= $.map(data, function(value, index) {
return [value];
});
this.messages= data_array;
});
}
sendMessage() {
this.db.list('/chat').push({
username: this.username,
message: this.message
}).then( () => {
this.message= '';
});
}
ionViewWillLeave() {
console.log('user is about to go');
this.obsToData.unsubscribe();
this.db.list('/chat').push({
specialMessage: true,
message: this.username + `has joined the room`
})
}
ionViewDidLoad() {
console.log('ionViewDidLoad ChatPage', this.username);
this.db.list('/chat').push({
specialMessage: true,
message: this.username + `has joined the room`
})
}
}
chat.html:
<ion-header>
<ion-navbar>
<ion-title>Chat</ion-title>
</ion-navbar>
</ion-header>
<ion-content padding>
<div id="chatMessages">
<div *ngFor="let message of messages" [class]="message.specialMessage ? 'message special': 'message'">
<div [class]="message.username == username ? 'innerMessage messageRight': 'innerMessage messageLeft'">
<div class="username"> {{ message.username }} </div>
<div class="messageContent"> {{ message.message }} </div>
</div>
</div>
</div>
</ion-content>
<ion-footer>
<div id="footer">
<ion-input type="text" [(ngModel)]= "message"> </ion-input>
<button ion-button icon-only (click)= "sendMessage()">
<ion-icon name="send"></ion-icon>
</button>
</div>
</ion-footer>
Looking at your code, you are getting the username from Storage.
That is an Async operation, which means it can happen before or after ionViewLoad event.
this means in the following lines
this.storage.get('username').then((val) => {
if (val != null) { // this will run later
this.username= val;
}
the constructor will exist without anything in the 'then' being executed. those lines will run later whenever the storage has the results ready.
think of it as putting an order for data. Just because you have put in the order, does not mean your order has arrived.
looks like by the time you leave the view, the call to storage has finished and username is set.
I suggest you wait somehow for the username to arrive like
<div id="chatMessages" *ngIf="username?.length > 0">
And even better that that, refactor your code so that you have username ready by the time you get to this view. after all this view is not usable without having a username ready.
I doubt you are using a tabbed layout. If that is the case, then try getting the stored result using ionViewDidEnter() so that everytime you enter the page the result gets updated. Try doing something like this and check,
ionViewDidEnter(){
this.storage.get('username').then((val) => {
if (val != null) {
this.username= val;
}
});
}

Ionic 3 Google Map does not display on Android + IOS

I use Ionic 3 version and I try to add a page into my app, to display a map with markers.
I already use for my app a Google Map Id for Autocomplete (Google places...).
I went to Google APIs and I added Map Embed, Javascript etc... to my API Key.
But The page appears with "Google" in the bottom and the display button", but the map is empty.
See attached file...
Install the Cordova and Ionic Native plugins:
$ ionic cordova plugin add https://github.com/mapsplugin/cordova-plugin-googlemaps#multiple_maps --variable API_KEY_FOR_ANDROID="AIzaSyB6mEnxH4vC+++++++++9wnXXNNmK2co" --variable API_KEY_FOR_IOS="AIzaSyB6mEnxH4v++++++++++++++wnXXNNmK2co"
$ npm install --save #ionic-native/google-maps
Home.ts:
import { NavController } from 'ionic-angular';
import { Component, ViewChild, ElementRef } from '#angular/core';
import { GoogleMaps, CameraPosition, GoogleMapsEvent, GoogleMap, MarkerOptions, Marker } from "#ionic-native/google-maps";
#Component({
selector: 'page-home',
templateUrl: 'home.html'
})
export class HomePage {
#ViewChild('map') mapElement: ElementRef;
map: any;
constructor(public navCtrl: NavController, private googleMaps: GoogleMaps) {
}
ngAfterViewInit() {
this.loadMap();
}
loadMap() {
// make sure to create following structure in your view.html file
// and add a height (for example 100%) to it, else the map won't be visible
// <ion-content>
// <div #map id="map" style="height:100%;"></div>
// </ion-content>
// create a new map by passing HTMLElement
let element: HTMLElement = document.getElementById('map');
let map: GoogleMap = this.googleMaps.create(element);
// listen to MAP_READY event
// You must wait for this event to fire before adding something to the map or modifying it in anyway
map.one(GoogleMapsEvent.MAP_READY).then(
() => {
console.log('Map is ready!');
// Now you can add elements to the map like the marker
}
);
// create CameraPosition
let position: CameraPosition = {
target: {
lat: 43.0741904,
lng: -89.3809802
},
zoom: 18,
tilt: 30
};
// move the map's camera to position
}
}
Home.HTML
Home.html :
<ion-header>
<ion-navbar>
<ion-title>
Map
</ion-title>
<ion-buttons end>
<button ion-button (click)="addMarker()"><ion-icon name="add"></ion-icon>Add Marker</button>
</ion-buttons>
</ion-navbar>
</ion-header>
<ion-content>
<div #map id="map" style="height:100%;"></div>
</ion-content>
Home.scss
page-home {
}
Do not use the ngAfterViewInit.
You must wait platform.ready()
// Wait the native plugin is ready.
platform.ready().then(() => {
this.loadMap();
});
Full code is https://github.com/mapsplugin/cordova-plugin-googlemaps-doc/blob/master/v2.0.0/ionic-native/README.md
Repo: https://github.com/mapsplugin/ionic-google-maps
The current official document page is wrong. I sent a pull request, but it's waiting now.
https://github.com/ionic-team/ionic-native/pull/1834
Please try below code and make sure you are using correct API key, write following code in your .ts file:
ionViewDidLoad() {
this.loadMap();
}
loadMap() {
let mapOptions: GoogleMapOptions = {
camera: {
target: {
lat: 43.0741904,
lng: -89.3809802
},
zoom: 18,
tilt: 30
}
};
this.map = this.googleMaps.create('map_canvas', mapOptions);
// Wait the MAP_READY before using any methods.
this.map.one(GoogleMapsEvent.MAP_READY)
.then(() => {
console.log('Map is ready!');
this.map.addMarker({
title: 'Ionic',
icon: 'blue',
animation: 'DROP',
position: {
lat: 43.0741904,
lng: -89.3809802
}
})
.then(marker => {
marker.on(GoogleMapsEvent.MARKER_CLICK)
.subscribe(() => {
alert('clicked');
});
});
});
}
In your .html file define map like below
<ion-content>
<div id="map_canvas" style="height: 100%;"></div>
</ion-content>

how to show data of model.ts in my code, using ionic 2

now i trying array data modeling using ionic2.
i created 'model.ts' in 'src/app/models' and declared in 'setting.ts' with array data.
next, i called it to 'setting.html'.
By the way...There is a some problem.
build and run were success. but any datas didn't show in screen..
not Error, i dont know where is wrong..
please find wrong point and fix that.
there is my code..
workoutlist-model.ts
export class WorkoutlistModel {
constructor(public Workoutlist: any[]) {
this.Workoutlist = [];
}
addItem(nm, gl) {
this.Workoutlist.push({
name: nm,
goal: gl
});
}
removeItem(nm, gl) {
for (var i = 0; i < this.Workoutlist.length; i++) {
if (this.Workoutlist[i].name == nm) {
if (this.Workoutlist[i].goal == gl) {
this.Workoutlist.splice(i);
}
}
}
}
}
setting.ts
import { Component } from '#angular/core';
import { NavController } from 'ionic-angular';
import { WorkoutlistModel } from '../../app/models/workoutlist-model';
#Component({
selector: 'page-setting',
templateUrl: 'setting.html'
})
export class Setting {
constructor(public navCtrl: NavController) {
new WorkoutlistModel([{ name: 'Push-Up', goal: 100 },
{ name: 'Squat', goal: 150 },
{ name: 'Sit-Up', goal: 45 }]);
}
}
setting.html - the part using this.
<ion-content style="height: 200px; outline: green">
<ion-card *ngFor="let WO of WorkoutlistModel;">
<button ion-item>
<div style="float: left;padding: 0px;">name : {{WO.name}}</div>
<div style="float: right;padding: 0px;">goal : {{WO.goal}}</div>
</button>
</ion-card>
</ion-content>
You havent declared or assigned WorkoutlistModel
Also WorklistModel is class and not an array to traverse with *ngFor
export class Setting {
workListModel:any;//declare
constructor(public navCtrl: NavController) {
this.workListModel = new WorkoutlistModel([{ name: 'Push-Up', goal: 100 },
{ name: 'Squat', goal: 150 },
{ name: 'Sit-Up', goal: 45 }]);//assign
}
}
In Html
<ion-card *ngFor="let WO of workListModel.getList();"><!-- get the list of items from class to traverse. may have to create this function -->
<button ion-item>
<div style="float: left;padding: 0px;">name : {{WO.name}}</div>
<div style="float: right;padding: 0px;">goal : {{WO.goal}}</div>
</button>
</ion-card>
You are not injecting the model. So change it to this.
constructor( Workoutlist: any[]) {
this.Workoutlist = Workoutlist;
}

Vue - Toggle class through multiple buttons

I have a Vue component, which displays a Vote button. When the user clicks on the Vote button an .active class gets added. Now I need to make sure only one Vote button can have this .active class at any given time.
What do I need to change in my code:
Vue.component('moustache', {
name: 'moustache',
props: ['type', 'img'],
template: `<li>
<p><strong>#{{ type }}</strong></p>
<img width="300" height="200" :src="img">
<button class="btn btn-primary" v-bind:class="{ active: isActive }" :data-type="type" #click="toggleClass">
Vote
</button>
</li>`,
data: function(){
return{
isActive: false
}
},
methods: {
toggleClass(){
this.isActive = !this.isActive;
}
}
});
new Vue({
el: '#app'
});
Each moustache component should only be in control of its own state. In the situation you describe, it would be best to let a parent component handle updating all the buttons when one is clicked.
In general:
Button is clicked in moustache component
Moustache component emits a "activated" event
The parent component listens for the "activated" event, and updates the state of all the buttons
Here is an example in code:
Vue.component('moustache', {
name: 'moustache',
props: ['type', 'img', 'isActive'],
template: `<li>
<p><strong>#{{ type }}</strong></p>
<img width="300" height="20" :src="img">
<button class="btn btn-primary" v-bind:class="{ active: isActive }" :data-type="type" #click="toggleClass">
Vote
</button>
</li>`,
methods: {
toggleClass() {
this.$emit('activate')
}
}
});
new Vue({
el: '#app',
data: {
buttons: [{
isActive: false,
type: 'Button 1',
img: null
}, {
isActive: false,
type: 'Button 2',
img: null
}, {
isActive: false,
type: 'Button 3',
img: null
}, ]
},
methods: {
activateButton: function(activatedButton) {
for (let button of this.buttons) {
button.isActive = button === activatedButton
}
}
}
});
.active { background-color: red; }
<script src="https://cdnjs.cloudflare.com/ajax/libs/vue/2.1.3/vue.js"></script>
<div id="app">
<moustache v-for="button in buttons"
:is-active="button.isActive"
:type="button.type"
:src="button.src"
v-on:activate="activateButton(button)"></moustache>
</div>
Of course, this approach only works if all of Vote buttons can be controlled by the same parent component. If you require more complex behaviour, then it might be worth looking into Vuex. Then the state of your buttons could be managed by a vuex store.