I'm developing HTML5 app with Knockout.js and JayData and I encountered a problem while implementing a pull-to-refresh data list.
I query a remote OData service with JayData API and assign the results to a KO observable array:
db.MySet
.orderBy('it.Field1')
.skip(recordsToSkip)
.take(20)
.toArray()
.then(function(result) {
self.MyDataList(result);
}
I always lose the old records. How could I fix my code?
I guess there is a small thing missing while bindig the result to the Knockout observable: check if the existing array already contains elements and append the result to the existing ones.
My colleague Viktor has a tutorial, which implements a highscore list with Knockout+JayData pull-to-refresh
db.MySet
.orderBy('it.Field1')
.skip(recordsToSkip)
.take(20)
.toArray()
.then(function(result) {
if (result.length){
if (self.MyDataList().length){
self.MyDataList(self.MyDataList().concat(result));
}else{
self.MyDataList(result);
}
}
});
Does this fix the app?
The full context of the example can be found on GitHub
You will need to concatenate your new array of objects to your old list:
.then(function(result) {
oldList = oldList.concat(result);
self.MyDataList(oldList);
}
(So, on the first run, you will need to set oldList = [])
Related
I'm working on an Angular 6 project where I'm loading data in from an AWS DynamoDB table via Observable into a Material Table component. I used the angular-cli to generate the initial structure of the table, and then added my own service to fetch external data, since the example used hard coded data in an array.
Everything seems to be working (I can see the correct data returned via console.log) except for the fact that on my initial load, the data that I'm returning from the observable isn't getting populated into the table. In fact if I inspect the "this.data" variable it seems like it's immediately getting set back to "undefined." If I select and change the number of results per page on the pagination component, the data returned by the observable is inserted.
connect(): Observable<DynamoTableItem[]> {
// Combine everything that affects the rendered data into one update
// stream for the data-table to consume.
const dataMutations = [
observableOf(this.data),
this.paginator.page,
this.sort.sortChange
];
// Set the paginators length
this.paginator.length = this.data.length;
return merge(...dataMutations).pipe(map(() => {
return this.getPagedData(this.getSortedData([...this.data]));
}));
}
I've put the project on stackblitz if someone wouldn't mind taking a look.
To reproduce the issue:
Go to: https://stackblitz.com/edit/mat-table-dynamo
Notice there is no data in the table.
Select the "Items per page" pulldown and change to a different value.
The table populates with the data returned from the Observable.
Thanks for your help!
The rule of thumb of writing any service in angular is that if you have a .subscribe() inside your service, you are probably(99%) do it wrong. Always return an Observable, and let your component do the .subscribe().
The reason why your code doesn't work is because you subscribe your data first inside your service, and then re-wrap it using Observable.of(). That won't work, because your http call is asynchronous. By the time your subscription inside your constructor has received emission, your connect has long established and this.data is first undefined before it can be assigned any values.
To solve your problem, simply change the of(this.data) to its original Observable source, and everything is working:
connect(): Observable<DynamoTableItem[]> {
// Combine everything that affects the rendered data into one update
// stream for the data-table to consume.
const dataMutations = [
this.dynamoService.getData(this.params),
this.paginator.page,
this.sort.sortChange
];
// Set the paginators length
this.paginator.length = this.data.length;
return merge(...dataMutations).pipe(map((received) => {
return this.getPagedData(this.getSortedData([...received]));
}));
}
Here is the working working StackBlitz
I've spent a while trying to track down a solution to this on Stack Overflow without any luck.
I'm using the Ionic 3 checked attribute in my html page as part of a list of contacts, as such..
<ion-list>
<ion-item *ngFor="let friend of friends_profile_data | async ;">
<ion-label>
<h2>{{friend.name}}</h2>
</ion-label>
<ion-checkbox [checked]="friend.checked" (click)="toggleItem(friend)" ></ion-checkbox>
</ion-item>
</ion-list>
..and have the toggleItem function in the associated .ts file:
toggleItem(friend): void {
friend.checked = !friend.checked;
}
In my test run with a dummy database I was able to successfully query this state of each 'friend' by using the following in the same .ts file (there was no | async in the ion-content line for the local dummy DB)..
this.friends_profile_data.forEach((friend) => {
var i =0;
var total = friend.month.length;
for(;i < total; i++ ) {
if(friend.checked) {
console.log(friend.name);
}
}
});
This runs through all the friends in my contact list, find the ones that I've selected, and gives me their names, not the ones that weren't selected. All good.
With that working fine, I've moved to hooking everything up to a Firebase database. Everything has been going well, except that when I tried to do a similar query as above within a Firebase subscribe function, it's not working. Here's what the code for that looks like..
this.friends_profile_data.subscribe(friends => {
friends.forEach((friend) => {
if (friend.checked) {
console.log(friend.name);
}
})
});
Where friends_profile_data in this case is returning a similar set of data as found in the dummy local database, but from my Firebase DB.
Instead of only returning the friends that I've selected in the list, it's logging every friends name.. as though it's not registering the friend.name in the IF statement.
I've tried using alternatives, in case any of them worked, such as:
friend.checked = true
friend.checked == true
friend.checked === true
..but nothing works.
Should this be working? Is there some issue I'm not aware of that's introduced by doing the query on the checked attribute within a Firebase subscribe function?
Alternatively, have I just been lucky in it working with the local dummy DB, and there's actually a more robust way of approaching this within the Firebase subscribe function? The use of the checked attribute approach is from a tutorial I'd been through a while back in early Ionic 2 days.
Thanks for any insight anyone can offer. As this is my first post, I've tried to be as informative as possible, but please let me know if there's anything else I can add that would help in trouble shooting the issue.
Before calling subscribe on the Observable, since this Observable contains an array of friends, you need to filter these friends using your rule first. You can use map function to transform the 'friends_profile_data'. It creates a new observable with the filtered content.
this.friends_profile_data
.map(array0 => {
return array0.filter(friend => friend.checked === true)
})
.subscribe(
...
);
I'm trying to understand how Meteor returns database records. I'm running the following code:
Template.body.helpers({
items(){
return Items.find({});
},
json(){
console.log(this.items());
},
loggedIn(){
return Meteor.userId();
}
});
I'm a little confused as to why this json method doesn't just output and array, or more specifically why the child values don't really seem to just return an array of values.
I can get the values inline html using spacebars, but I'm not sure how to access those values through js. What simple thing am I missing here?
Collection.find() in Meteor returns a cursor, which is a function that can be used by Blaze templates (for example).
Collection.find().fetch() returns an array of objects (i.e. documents).
If you want to parse the database record between multiple helpers or even between templates and routes, why don't you use session variables.
For your example:
Template.body.helpers({
items(){
const items = Items.find({});
Session.set('itemArray', items);
return items;
},
json(){
console.log(Session.get('itemArray');
},
loggedIn(){
return Meteor.userId();
}
});
Does this work for you?
I want to update my DOM element everytime the value on the Firebase changes. I've seen that Angularfire handles three-way data binding, but from what I understood it only works if you take elements from $firebaseArray directly from the DOM.
What I have is an Element on the DOM (chart) that depends on some of the data on a $firebaseArray, but my element gets the data from a function instead of directly from the $firebaseArray. That means I have to do some pre-processing on the $firebaseArray before my element can use it.
This is what I have:
<pie-chart ng-repeat="chart in myCtrl.charts"
data="chart.data"
options="chart.options"></pie-chart>
This is my controller:
function MyCtrl($firebaseArray) {
let myRef = new Firebase(refUrl);
let chartsFirebase = $firebaseArray(myRef);
let getCharts = function() {
let charts = [];
distanceGoals.$loaded().then(function() {
// push some things from chartsFirebase on the charts array
charts.push({
options: { ... },
data: [ ... ]
});
}
return charts;
}
this.charts = getCharts();
}
Turns out that in this way this.charts is only updated one time, after modifications on the data in Firebase I have to refresh the browser.
Has anyone an idea of what I could do to achieve this behavior?
You can add a child-changed event listener to your ref like this:
// Get a reference to our posts
var ref = new Firebase("https://docs-examples.firebaseio.com/web/saving-data/fireblog/posts");
// Get the data on a post that has changed
ref.on("child_changed", function(snapshot) {
var changedPost = snapshot.val();
console.log("The updated post title is " + changedPost.title);
});
This will get called everytime something changes in the location you put the listener on.
For more info take a look at https://www.firebase.com/docs/web/guide/retrieving-data.html and https://www.firebase.com/docs/web/api/query/on.html.
From the AngularFire documentation on $loaded()(emphasis mine):
Returns a promise which is resolved when the initial array data has been downloaded from the database.
That explains the behavior you're seeing.
To solve this, you should extend the $firebaseArray as documented here: https://www.firebase.com/docs/web/libraries/angular/guide/extending-services.html#section-firebasearray
Some related questions:
AngularFire extending the service issue
Joining data between paths based on id using AngularFire (includes a full example by the author of AngularFire)
I am making a project with Meteor and I'm having some issues trying to get data out of mongodb in JavaScript. I have the following in a function:
console.log(Time.find({today: "Saturday"}).fetch());
In my publish.js file on the server side I have the following:
Meteor.publish("time", function () {
var currentUserId = this.userId;
return Time.find({user: currentUserId});
});
And In my subscriptions file I have the following:
Meteor.subscribe("time");
This function gets called later down in the code but it returns an empty array. If I run this code in my browsers console it returns an array with 2 objects in it, which is correct. This leads me wondering if I can use the .fetch() function from within my code? As if I leave off the .fetch() it returns what looks like the usual giant object. My real problem is I need the data in the form that .fetch() gives it to me in. I think it's because the function gets triggered before the data gets a chance to load in, as if I switch out the .fetch() for a .count() it returns 0.
Is there any way around this or a fix?
Where are you you running that console.log?
There are a couple fundementals here that I believe you may have glossed over.
1 Pub / Sub
This is how we get data from the server, when we subscribe to a publication i becomes active and begins to send data, this is neither instant or synchronous, (think of it more like turning on a hose pipe), so when you run your console.log, you may not yet have the data on the client.
2 Reactive contexts
One of the fundamental aspects to building anything in meteor is its reactivity. and it helps to start thinking in terms of reactive and non reactive contexts. A reactive context is one that re-runs each time the data it depends on changes. Using an autorun (Tracker.autorun or this.autorun insdie a template lifecycle callback) or a template helper are good examples. By placing it in a template helper it will re-run when the data is available.
Template.Whatever.helpers({
items: function() {
// ...do your find here.....
}
});
As items is a reactive context, depending on the collection data, it re-run when that changes, giving you access to the data when the client has them.
3 Retrieving Non Reactive Data
Alternatively it is also possible to retrieve data non-reactively by using Meteor.call with a meteor method, and then doing something with the result, in the callback to the Meteor.call. Depending on what you're doing, Meteor.wrapAsync may also be your friend here.
a simple example (out of my head, untested) :
// on the server
Meteor.methods({
gimmeStuff: function() {
return "here is your stuff kind sir!";
}
});
// on the client
Meteor.call('gimmeStuff', function(err, result) {
if (err || !result) {
console.log("there was an error or no result!");
return false;
}
console.log(result);
return result;
});
4 Its Unlikely that you actually need ithe .fetch()
If you're working with this in a template, you don't need a fetch.
If you want this to be non-reactive you don't need a fetch
As one of the commenters mentioned, a cursor is just a wrapper around that array, giving you convenient methods, and reactivity.
5 Go Back to the Begining
If you haven't already, I would highly recommend working through the tutorial on the meteor site carefully and thoroughly, as it covers all of the essentials you'll need to solve far more challenging problems than this, as well as, by way of example, teach you all of the fundamental mechanics to build great apps with Meteor.