I have beat my head for a couple days...trying to use the (ionItemReorder)="reorderItems($event)" to reorder a list. I have a list of songs I'm getting from FireBase. When I fire the reOrderItems click event I get an error: TypeError: array.splice is not a function at reorderArray
I assume it's probably something very simple in the way I'm defining "songs". I have tried several different ways...but at this point I'm just grasping at straws.
Any suggestions would be greatly appreciated. Thank you! ER
Typescript:
import { Component } from '#angular/core';
import { IonicPage, NavController, NavParams, reorderArray } from 'ionic-angular';
import { AngularFireModule} from 'angularfire2';
import { AngularFireDatabase } from 'angularfire2/database';
#IonicPage()
#Component({
selector: 'page-songs',
templateUrl: 'songs.html',
})
export class SongsPage {
//songs: any = {};
//songs = {};
//songs = [];
//songs: any = [];
songs: any;
btnName: any = 'Reorder';
flag: any = false;
constructor(
public navCtrl: NavController,
public navParams: NavParams,
public afd: AngularFireDatabase
)
{
this.songs = this.afd.list('/songs/').valueChanges();
}
//Button in navbar to toggle reordering the list of songs
actionBtn(){
if (this.btnName == 'Reorder') {
this.btnName = 'Done';
this.flag = true;
}
else{
this.btnName = 'Reorder';
this.flag = false;
}
};
reorderItems(indexes){
//let element = this.songs[indexes.from];
//this.songs.splice(indexes.from, 1);
//this.songs.splice(indexes.to, 0, element);
this.songs = reorderArray(this.songs, indexes);
};
showChords(song){
this.navCtrl.push('ChordsPage', song)
}
}
HTML:
<ion-header>
<ion-navbar>
<ion-title>Songlist</ion-title>
<ion-buttons end>
<button ion-button small clear (click)="actionBtn();">
{{btnName}}
</button>
</ion-buttons>
</ion-navbar>
</ion-header>
<ion-content>
<ion-list reorder="{{flag}}" (ionItemReorder)="reorderItems($event)">
<ion-item-sliding *ngFor="let song of songs | async ; let i = index">
<ion-item>
<h2>{{i+1}}. {{ song.Title}}</h2>
<p>{{song.Artist}}</p>
</ion-item>
<ion-item-options side="right">
<button ion-button (click)="showChords(song)">Chords</button>
</ion-item-options>
<ion-item-options side="left">
<button ion-button color="danger" (click)="removeSong(song)">Delete
<ion-icon name="trash"></ion-icon>
</button>
</ion-item-options>
</ion-item-sliding>
</ion-list>
</ion-content>
Subscribe to the Observable and map the items to be pushed into the songs array. Also, use the lifecycle hook ionViewDidLoad instead of the constructor for doing things at initialization.
import { AngularFireDatabase } from 'angularfire2/database';
import { ISubscription } from 'rxjs/Subscription';
import { OnDestroy } from '#angular/core';
import { Observable } from 'rxjs/Observable';
...
export class ... implements OnDestroy {
songslist: Observable<any[]>;
subscription: ISubscription;
songs: any[];
constructor(...
ionViewDidLoad() {
this.songslist = this.afd.list<Item>('songs').valueChanges();
this.subscription = songslist.subscribe(items => {
return items.map(item => { this.songs.push(item); });
});
}
...
reorderItems(...
...
ngOnDestroy {
this.subscription.unsubscribe(); // Make sure to unsubscribe, don't leave them open.
}
}
Trying to accomplish something very similar - except with Firestore instead of the RealTime Database - had me wanting to bang my head against the wall, but finally got it working so hopefully it's helpful to you or others even though some things will be slightly different.
The array.splice is not a function error comes up when you are trying to perform the function on something that isn't an array, in this case the observable which for reasons beyond my understanding seems to be returned as a single object.
To get things working I declared each of these variables separately:
photoCollectionRef:AngularFirestoreCollection < PhotoSlide>;
photo$: Observable < PhotoSlide [] >;
photo = {} as PhotoSlide;
photoArray: any=[];
Then set each value as follows
this.photoCollectionRef = this.afs.collection('posts').doc(this.postId).collection('photos/', ref => ref.orderBy('index'));
this.photo$ = this.photoCollectionRef.valueChanges();
this.photo$.take(1).subscribe((pictures) => {
pictures.forEach(p=>{
return this.photoArray.push({index:p.index, photoURL: p.photoURL, id:p.id })
})
});
In the template file *ngFor loops over photo of photoArray (without an async pipe) and the reorderItem function called from the item group works perfectly with standard single line syntax from the ionic docs.
Then to update the indexes in the Firestore Collection, I call the below function from a button that's visible once the list order has been modified:
saveNewOrder(){
this.photoArray.forEach((element, i) => {
this.photoCollectionRef.doc(element.id).update({
index: i
});
});
}
I should note that I also always use the .createId() method and then .doc(newId).set({id:newId etc}) when adding new documents rather than just .add because I find it a lot less annoying to do that than be stuck using .snapshotChanges and map function rather than just .valueChanges() - which is why the above functions work with the syntax shown
Also if you don't use the rxjs take(1) operator before the subscription it all looks fine at first but after the saveNewOrder function's executed the list repeats several times
I am late getting back to this but here is my attempt at explaining how I got this working.
**Caveat - I am a hobbyist coder so I apologize in advance if I'm explaining using wrong terms or if I just understand it wrong..the code works:).
The goal is to get a list from Firebase using the NEW version of Firebase and pull it into an array so that I can present it to the screen and then use the reorderArray function to keep the array indexes in order after moving items around in a list. So if I have an array of songs...I'll call it songList (think no Firebase yet) and assume this is my HTML:
<ion-list reorder="true" (ionItemReorder)="reorderItems($event)">
<ion-item *ngFor="let song of songList; let i = index">
{{i+1}}. {{song.title}} - {{song.artist}}
</ion-item>
</ion-list>
Then I have the standard function to reorder the array:
reorderItems(indexes) {
this.songList = reorderArray(this.songList, indexes);
};
Add the 'reorderArray' to the import to enable this:
import { NavController, AlertController, reorderArray } from 'ionic-angular';
After looking into this a little I think the reorderArray function does a few .splice commands to get the indexes moved around in the array.
So fast forward to replacing my array with a Firebase list. See all the above code from the 1st post...all that works to get the list to show up on the HTML page. But as soon as the reorderArray is fired I get "splice" errors thrown. As it turns out the Firebase list at this point is an object and the reorderArray expects an array. I got the below code from a Youtube video:
//Set up the songList array
var x = this.afDatabase.list('/songs');
x.snapshotChanges().subscribe(item => {
this.songList = [];
item.forEach(element => {
var y = element.payload.toJSON();
y["fbKey"] = element.key;
this.songList.push(y);
})
})
I will try to explain as best I can what this is doing. I set x to be a ref to my Firebase list /songs. I invoke snapShotChanges() to get my list values and the key. I then subscribe to the list to walk through the items. I declare setList as an array. I iterate over the list of items and I guess 'payload' is a special property that gets all the object data?? I think I cast all that object data into an array. I add a new field to the list I think so I can get back at the .key value from a field? I then push all that data into an array.
Again I'm not sure how all this magic works but it does. Now I have an array in songList that holds all my data...and now I can use the reorderArray function to keep the indexes straight on the client side after a reorder.
But...new problem. There is no client side representation of that index value out in the Firebase list.
The rest of this code is a little hazy as when things started working I was all over the map and adding lots of stuff to see it work. Right now I'm having Ionic Serve issues and can't get this running right now without deploying it up to Firebase or Ionic View...so I have to go by memory.
So here is what my final HTML looks like:
<ion-list reorder="true" (ionItemReorder)="reorderItems($event)">
<ion-item *ngFor="let song of songList | orderBy: 'sortOrder'; let i = index">
{{i+1}}. {{song.title}} - {{song.artist}} ({{song.sortOrder}})
</ion-item>
</ion-list>
The only real difference here is that I have an orderBy on a new field called sortOrder.
Here's how all this works:
reorderItems(indexes) {
var luTitle = '';
var luArtist = '';
this.songList = reorderArray(this.songList, indexes);
this.songList.forEach( song => {
this.afDatabase.database.ref('songs/' + song.fbKey + '/sortOrder').set(this.songList.indexOf(song));
this.afDatabase.database.ref('songs/' + song.fbKey)
.on('value', function(snapshot) {
luTitle = snapshot.child('title').val();
luArtist = snapshot.child('artist').val();
})
console.log("Index: " + this.songList.indexOf(song));
// console.log("Title: " + song.title);
console.log("LU Title: " + luTitle);
console.log("LU Artist: " + luArtist);
console.log("FB Key: " + song.fbKey);
console.log("Sort Order: " + song.sortOrder);
})
};
A lot of this is just logging stuff to the console but the real work is this:
The first thing I do is run the reorderArray over the this.songList and it gets all the indexes in the right place on the client side. Then I iterate over all the items in that array and I create a reference to the FB list using the song.fbKey that we set when we converted the initial FB list to an array. Then I .set a sortOrder field for that song equal the the current index of that song as it exists on the client side at that moment in time. Everything else I think after that is me logging stuff to the console to look at values. The LU (Look Up) stuff was just me figuring out how to get a value back in from Firebase.
Now the orderBy in the ngFor immediately orders everything by the sortOrder field that basically comes in real time from FB. I can't remember but I think if the list is brand new from FB and there is no sortOrder field yet it defaults to sorting by the key...which is fine...the first time reorder is fired all the sortOrders get set.
I'm sure there are some bugs I will discover when I get back to this...but it's working code as of now.
Thanks for reading if you made it this far.
ER
Related
im trying to code a simple step counter app, im using this plugin (https://github.com/leecrossley/cordova-plugin-pedometer) in android to get the step counter data, so i want to get this data and show it on my ionic page (front end view), i tryed to use ngZone to refresh the object while the method subscribe to the startPedometerUpdates function, but it takes many seconds to work and sometimes stucks few seconds and then start to show the counter again...
this is my code:
steps: any = []
constructor(private ngZone: NgZone, private pedometer: Pedometer) { }
ngOnInit() {
}
getSteps(){
this.pedometer.startPedometerUpdates()
.subscribe((data: IPedometerData) => {
this.ngZone.run(() => this.steps.push(data))
});
}
my html is simple for debug the data:
<ion-content>
{{this.steps | json}}
<ion-button (click)="getSteps()">Show steps</ion-button>
</ion-content>
so i want to show the data in "real time" with something simple as posible...
thanks in advance
you can't push data in subscribe even if you using NgZone bcoz push is a function ngZone doesn't work in the function
steps: any = []
constructor(private ngZone: NgZone, private pedometer: Pedometer) {
this.getSteps();
setInterval(()=>{
console.log('read in a sec')
},1000)
}
ngOnInit() {
}
getSteps(){
this.pedometer.startPedometerUpdates()
.subscribe((data: IPedometerData) => {
this.ngZone.run(() => this.steps.push(data))
});
}
I have a nav component which I am using on 4 pages, I want to be able to change the color of active page's button in the nav component. In Ionic app doc's for nav controller I found getActive() instance, but I can't figure out how to achieve the desired result with it. I'm using the following code to push to a new view.
viewPage2(){
this.navCtrl.push(Page2);
}
<button ion-button (click)="viewPage2()" color="dark" clear full>Page 2</button>
NavController getActive() returns the ViewController of the Active page.
Looking at the API of ViewController you could try using getContentRef():
this.navCtrl.getActive().contentRef().nativeElement.getElementById("button_id")
Once you have the element you could change the color.
Even though getting the html element by its id may work, modifying the DOM directly is not the recommended way to do things in Ionic.
First option:
If that's a custom component, you can always expose a public method in that component, and get the reference by using ViewChild
#Component({...})
export class NavCustomComponent {
public activePage: string = 'page1';
//...
public changeActivePage(pageName: string): void {
this.activePage = pageName;
}
// ...
}
And in your view:
<button ion-button (click)="viewPage2()" [color]="activePage === 'page2' ? 'light' : 'dark'" clear full>Page 2</button>
Then in the page where you're trying to modify the component:
#Component({...})
export class DemoPage {
#ViewChild(NavCustomComponent) navCustomComponent: NavCustomComponent;
}
and then use that reference to call that public method:
this.navCustomComponent.changeActivePage('page2');
Second option:
If that's not a custom component, or you just want to make things even simpler, you can just Events. Whereever you're defining the code of that nav component, (or in your app.component.ts file to make it global for the entire app) subscribe to the event:
public activePage: string = 'page1';
constructor(public events: Events, ...) {
events.subscribe('page:selected', (pageName) => {
this.activePage = pageName;
});
}
Again, in your view:
<button ion-button (click)="viewPage2()" [color]="activePage === 'page2' ? 'light' : 'dark'" clear full>Page 2</button>
And then in the component where you want to change the color, just publish that event:
events.publish('page:selected', 'page2');
I want to create a dropdown list with searchbar with the list containing data from a REST API.
Initially in the searchbar I created I could just select the item but couldn't display that in the searchbar. I want the selected item to be displayed in the searchbar.How can I display that selected item.
I need to display it because I'm building a cascading dropdown list where the input of the first list is served to the second list.
I'll be thankful if someone can provide me the code by putting mind that the data is being rendered from a REST API using POST method.This is my function which gets called in the html page.
userData is an array of type object since I need to get JSON data.I get an error saying property toLowerCase cannot exist on of the type Object.
loadusers(ev:any){
this.UserService.post('search/getusers',{}).subscribe(resp=>{
this.userData = resp.data;
console.log(this.userData);
},
err=>{console.log(err);},
()=>{});
let val = ev.target.value;
// if the value is an empty string don't filter the items
if (val && val.trim() != '') {
this.userData = this.userData.filter((item) => {
return (item.toLowerCase().indexOf(val.toLowerCase()) > -1);
})
}
}
This is the HTML file where I'm trying to display the data along with a searchabar.I also don't know what to include in the isChanged function.
<ion-searchbar [(ngModel)]="mySearchInput" (ionInput)="loadusers($event)"></ion-searchbar>
<ion-content >
<ion-list radio-group [(ngModel)]="selectedValue">
<ion-item class="border" *ngFor="let val of userData">
<ion-label>{{val.name}}</ion-label>
<ion-radio class="resolvedState" value="{{val.name}}" (ionSelect)="isChanged(val)"></ion-radio>
</ion-item>
</ion-list>
What is the best practise to create a searchbar with these features ?
I am developing an application on Ionic2/rc0. I got a ReplaySubject on a singlenton service that keeps the current user consistent across the whole app. It all works fine, I can subscribe to it and get a User object as easy as
this._user.Current.subscribe(user=>{ console.log(user)});
The User object looks like this
User {
confirmed:true
devices:["57f65werwe343bn8843f7h","7yr3243h5429hf2hjd"]
friends:["t245y53h65346htyh","356ytrer75dfhg43we56df"]
email:"francescoaferraro#gmail.com"
id:"57f6525e926bbc7615fc5c5c"
notification:false
password="$2a$04$.Fk/8eMj18ZrkfurbbdP4uT3yOs7Lb9db74GkNfgtABVY.ez2Q0I."
picture:"https://api.cescoferraro.xyz/kitty"
role:"master"
username:"cesco"
}
As you can see my backend is using MongoDB with One-to-Many Relationships with Document References as described here.
I have created a devices tab where I want to display all data about those user devices, but I need to call this._devices.info for each one of current.devices and concat the result back to TrueDevices
#Component({
template: `
<ion-header>
<ion-navbar>
<ion-title>Tabs</ion-title>
</ion-navbar>
</ion-header>
<ion-content>
<h2>Device:list</h2>
<h2 *ngFor="let item of devices | async">{{item}}</h2>
<button ion-button (click)="readDevice()">Read Random Device</button>
</ion-content>
`
})
export class DeviceComponent {
devices: Observable<string[]>;
TrueDevices: Observable<Device[]>;
constructor(public _user: UserService, public _device: DeviceService) {
this._user.Current.subscribe(user=>{ this.devices = Observable.of(user.devices)});
// Get current User
// call this._devices.info for each one of current.devices
// concat the result back to TrueDevices
this._user.Current
.subscribe((result) => { console.log(result) });
}
readDevice(){
console.log(this.devices);
this._device.info(this.devices.value[0]).subscribe(data=>console.log(data))
}
}
I will need to repeat the same procedure to the friends tab and so on. I am pretty sure there are a couple operators that would do the magic, but I am fairly new to rxjs and not familiar with all of them. Whats the right approach?
this._user.Current
.switchMap(user => Observable.from(user.devices)) // after this line, you have an Observable<string>
.mergeMap(device => this._device.info(device)) // each device will be mapped to another observable(or stream), and all the streams will be merged together
.toArray() // wait for all the streams to complete and reduce all the results into an array.
.subscribe(array => console.log(array));
or go to the gitter room:
https://gitter.im/Reactive-Extensions/RxJS
I'm trying to use collection-repeat to display an angular component for each object in an array. I pass each object as parameter to an angular component but when I try to access the object in my component's controller I get undefined.
<ion-content>
<ion-list>
<ion-item
collection-repeat="user in users"
item-width="100%"
item-height="90px">
{{user}} //renders my user data correctly instantly
<usser user="user"></user>
</ion-item>
</ion-list>
</ion-content>
My component
angular
.module('app')
.component('user', {
templateUrl: 'components/user.html',
scope: true,
bindings: {
user: '<'
},
controller: function() {
console.log(self.user) //prints undefined
}
})
I've tried wrapping the console.log in a $timeout without success
Printing self displays {user: undefined} in my chrome console, but if I expand the object I can see that user contains the correct data (only for the some of the items)
Accessing self.user doesn't work
EDIT: I can't really understand what's going on..
controller: function() {
console.log(self.user) //prints undefined
setTimeout(function() {
console.log(self.user) // prints my data
}, 2000)
}
What am I doing wrong?
Thanks in advance!
Lost 3 hours to figure out this
This is a known issue at the moment with collection repeat. A fix
would require a refactor of collection repeat, which would be too big
of a change the moment.
Always check the issues on Github, [V] lesson learned