I'm using firebase to save posts that have the following data:
createdAt: "Sun Apr 03 2016 18:32:46 GMT-0300 (BRT)"
What I'm trying to achieve is to get the most recent posts first and then load the older ones while the user scrolls down.
With posts retrieved using ngInfiniteScroll I'm being able to order desc using <div ng-repeat="post in posts | orderBy:'-createdAt'"> but ngInfiniteScroll keep returning the old posts first. I'm ordering but i'm ordering the older ones.
I already tried using the same logic ("-createdAt") in ngInfiniteScroll but it was not effective.
My js is pretty much this:
var baseRef = new Firebase(FBURL).child("posts");
var scrollRef = new Firebase.util.Scroll(baseRef, "createdAt");
$scope.posts = $firebaseArray(scrollRef);
$scope.posts.scroll = scrollRef.scroll;
Security and rules:
"posts": {
".read": true,
".indexOn": "createdAt",
"$post": {
".read": true,
".write": true,
"$other": {
".validate": true
}
}
}
Looks like you found your solution but you were on the right track piping with the orderBy but just try tweaking it a little. Try using orderBy:'+':true", where orderBy this says you want to order it by something and the +:true says order it where anything new is on top, where -:true would say order new content on the bottom. But you can look the angular docs for it here. So if where handling it on the HTML side it would look something like this ng-repeat="post in posts| orderBy:'+':true" or:
<div ng-repeat="post in posts| orderBy:'+':true">
[....post info here]
</div>
But give that a try, hope it helps.
After looking a little deeper at the available documentations I could find an ugly workaround here.
It is basically to use a negative timestamp and retrieve the createdAt normally (ascending).
So when saving the data I'm doing the following:
{
createdAt: Firebase.ServerValue.TIMESTAMP,
createdAtDesc: 0 - Date.now()
}
Still looking for a better solution.
Related
I'm a newbie at angularjs and facing a problem.
I'm using an angular calendar (not a creation of mine).
Passing an object array (events) is mandatory for using it's directive in my code.
In templates of this plugin I can "consume" this array of object.
My issue is located here.
The array (eventArray) is composed of object like this one:
currentEvent = {
_id: bookingArray[i]._id,
private: bookingArray[i].private,
space: space_name,
user: user,
isOwnBook: $scope.connected_user._id === user._id,
title: resource_title,
startTime: new Date( bookingArray[i].ts_start),
endTime: new Date( bookingArray[i].ts_end),
allDay: false,
deleted: false,
};
Let's say I log each currentEvent.title in the array, console would be like this:
RoomA RoomB RoomC RoomA RoomB RoomZ ...
I want to create a div each time the currentEvent.title is different.
<span ng-repeat="currentEvent in dt.eventArray">{{currentEvent.title}}</span>
This previous code should print only:
RoomA RoomB RoomC RoomZ
How should I proceed in order to obtain this behavior ?
Best regards !
I manage to find out a solution.
You need to install angular-filter first.
Then the code in the template is like this:
<span ng-repeat="(key, value) in dt.eventArray | groupBy: 'title'">{{key}}</span>
And finally you get as many span as title variation
I need to get a list of all Features that belonged to a specific Release on a specific date. I know basically how to use the lookback API and I can use it get the Release object for the specific date. The problem is the Release Object doesn't seem to contain a Children element. This is according to the official documentation.
So I tried pulling all of the features for the whole project in hopes of being able to filter by Release.ObjectID, but anytime I try to filter by Release.ObjectID the response is null. Not an empty array indicating no matching records but actual null. I have probably tried a dozen different ways.
This is how the code looks now, with all attempts to filter by Release.ObjectID removed. Can anyone point out how to get this done as part of the query, or am I going to have to load all of the features and then manually filter them?
_lookbackFeaturesByRelease: function(){
var scope = this.getContext().getTimeboxScope()
var ReleaseID = scope.record.raw.ObjectID;
var ProjectID = this.getContext().getProject().ObjectID;
this.snapshot = Ext.create('Rally.data.lookback.SnapshotStore', {
autoLoad: true,
limit: Infinity,
params: [removeUnauthorizedSnapshots = 'true'],
find: {
_ProjectHierarchy: ProjectID,
_TypeHierarchy: "PortfolioItem/Feature",
__At: "2017-02-10T00:00:00Z"
},
fetch: ['ObjectID', 'Name', 'Release'],
hydrate: ['Release'],
listeners: {
load: this._processlbR,
scope: this
} //End Listeners
});//End snapshot create
},//End _lookbackRelease
I think you can just add something like this to your find:
find: {
Release: { '$in': [12345, 23456] }
}
Where 12345 and 23456 are the release objectid's that correspond to your selected release and releases in child projects. You'll need to query wsapi to find all the release oids in your current project scope and pass those values.
I'm following the Meteor To-Do App tutorial with Angular integration and am learning about filtering collections. I've been able to implement a simple filter on a collection for the app I'm working on by following the principles in the tutorial, but now I'm stuck trying to figure out how to add multiple queries to the filter.
In the example, you can view incomplete tasks by toggling a checkbox. This is implemented in the controller by watching $scope.hideCompleted for changes and passing it as a Mongo query to filter the Meteor collection.
Watcher
$scope.$watch('hideCompleted', function() {
if ($scope.hideCompleted)
$scope.query = {checked: {$ne: true}};
else
$scope.query = {};
});
Collection filter
$scope.tasks = $meteor.collection(function() {
return Tasks.find($scope.getReactively('query'), {sort: {createdAt: -1}})
});
How do I make the query support multiple filters? For example, say I've chosen to extend the example and have ranked each to-do item by priority. I then would have an an input field for the user to filter the collection by priority, whose value is bound to $scope.priority. Now, if I wanted to filter the to-do list by incomplete and priority=$scope.priority tasks, I understand the Mongo query would need to be something along the lines of Tasks.find({ $and: [{ checked: {$ne: true} },{ priority: $scope.priority }]},{ sort: { createdAt: -1 } }).
In my app, I've been able to make two watchers properly track changes to two scope variables, analogous to my example with $scope.hideCompleted and $scope.priority, but I don't know how to take the next step to merge the queries when filtering the collection. I've also tinkered around a little with this package, since I'll eventually hope to be able to filter and sort by many criteria, but I didn't get too far with it before switching to the concepts I've described here.
I'd appreciate any help with this. Thank you!
This would be my approach:
$meteor.autorun($scope, function() {
// uncomment subscribe after you've got to that point
// $scope.$meteorSubscribe('yourSubscription').then(function() {
$scope.tasks = $scope.$meteorCollection(function() {
return Tasks.find({
checked: $scope.getReactively('model.hideCompleted'),
priority: $scope.getReactively('model.priority')
}, { sort: { createdAt: -1 } });
});
// });
});
A couple of things here:
Once you have removed autopublish you can uncomment the $scope.$meteorSubscribe method and replace "yourSubscription" with the name of your actual subscription.
$meteor.autorun will fire every time any getReactively variable changes.
$scope.$meteorSubscribe and $scope.$meteorCollection are favored as they will remove the subscriptions and object/collection when the scope is destroyed.
If you have any issues then perhaps I can setup a demo for you to look at.
Well, I guess I was a lot closer than I had expected, so I'll answer my question and share what I did to implement multiple filters with regards to the hypothetical extension of the to-do app.
I made hideCompleted and priority scope variables into properties of a scope object model, and used a single watcher with the argument true at the end to check for object equality (for any changes to model or its properties). I then generated $scope.query by stitching together "sub-queries." I've added the code below.
This seems to be working fine for now, but I'm not sure if it's the best solution, so I will continue experimenting, and I will update my answer if I find anything better. I'd be interested in any other approaches, though!
Watcher
var initQuery=true;
var subQueries=[];
$scope.$watch('model', function() {
if (!initQuery){
subQueries=[];
if ($scope.model.hideCompleted)
subQueries.push({checked: {$ne: true}});
if ($scope.model.priority)
subQueries.push({priority: $scope.model.priority});
$scope.query = { $and: subQueries};
} else {
initQuery = false;
$scope.query = {};
}
}, true);
Filter Collections (unchanged)
$scope.tasks = $meteor.collection(function() {
return Tasks.find($scope.getReactively('query'), {sort: {createdAt: -1}})
});
I am creating a simple list of messages similar to "Whatsapp". I want to include small blurb saying today, yesterday etc.
JSON format:
{
"id":"2",
"chat_id":"2",
"msg":"sample message 1",
"timestamp":1404803173
}
HTML:
<ul>
<li class="time"><span>Today/Yesterday/2DaysAgo</span></li>
<li ng-repeat="result in results">{{result.msg}}<small>{{result.timestamp*1000 | date:'medium'}}</small></li>
</ul>
I want to show the first <li> (class="time") only once for the day (not for all message). Is there any better way I can do this?
Do you have a server side? If yes, the best way to do this would be to:
Show the "time" li upon first load every new day.
set a flag on the server side to "true" once the li is loaded
Set the flag to false at 0000 hrs every night (or at first load every morning).
Check upon every load if the flag was set to true that day already.
Not sure if this can be done purely from client side. One way thats worth a show would be to set the flag in LocalStorage once every day (append the date maybe? ) and check this flag upon each load..
IMHO the best approach would be to sort messages by time then to split them by day and as output repeat days and messages in days, with other approaches it can be really overcomplicated and not worth the time
Considering that this JSON is coming from a Service and it is an array:
var json=[
{
"id":"1",
"chat_id":"2",
"msg":"sample message 1",
"timestamp":1404803173
},
{
"id":"2",
"chat_id":"2",
"msg":"sample message 2",
"timestamp":1404803174
},
...]
then you could create a new object on your controller looping through the items from your JSON and adding a unique key value for all the days, so you can filter it e.g.: the dates without "/"
$scope.json = Service.json;
$scope.dates = [];
$scope.messages = [];
for(i=0; i<$scope.json.length; i++){
var d = new Date($scope.json[i].timestamp*1000);
var index = String(d.getDate()) + String((d.getMonth()+1)) + String(d.getFullYear());
var obj = {id: index, data: $scope.json[i]};
$scope.messages.push(obj);
if($scope.dates.indexOf(index) == -1) $scope.dates.push(index);
}
Then, on your view:
<ul ng-repeat="date in dates">
<li class="time"><span>{{date | someFilterToGetDayFromDate}}</span></li>
<li ng-repeat="message in messages | filter:{'id': date}:true">{{message.msg}}<small>{{message.timestamp*1000 | date:'medium'}}</small></li>
</ul>
I didn't test the code, but hopefully you will get the idea of what to do here.
If it is not clear, let me know and I will try to explain further.
I recently started learning AngularJS+Firebase. I'm trying to write in my firebase an object like this:
{
title: "Personal Information",
say: [
[{ "eng": "What's", "ukr": "Що є" }, { "eng": "your", "ukr": "твоє" }, { "eng": "surname?", "ukr": "прізвище?" }],
[{ "eng": "Smith", "ukr": "Сміт" }],
[{ "eng": "What's", "ukr": "Що є" }, { "eng": "your", "ukr": "твоє" }, { "eng": "first", "ukr": "перше" }, { "eng": "name?", "ukr": "ім'я?(не фамілія)" }]
]
}
with line:
lessondata.add($scope.topic);
where 'lessondata' is service created with angularFireCollection() and $scope.topic - object bound to my UI.
But got the following error:
Firebase.push failed: first argument contains an invalid key ($$hashKey) in property 'say.0.0'. Keys must be non-empty strings and can't contain ".", "#", "$", "/", "[", or "]"
As I understood Firebase do not allow to use 0 as a key even if it's a key in an attached array for which zero key is natural. So should I change my object structure in some hardcoded instance or I miss something? Thanks in advance!
EDIT: As Anant points out in the comments, in the latest stable version of Angular (1.0.7 atm), you can use angular.copy(obj) to remove $$hashkey attributes.
Like Michael said, the '$' in '$$hashKey' is the issue. Angular creates the $$hashKey properties behind the scenes (see more here: https://groups.google.com/forum/#!topic/angular/pI0IgNHKjxw). I've gotten around this issue by doing something like myRef.push(angular.fromJson(angular.toJson(myAngularObject))).
The issue is the $ in "$$hashKey", not the 0. 0 is allowed.
I wanted to throw another answer in here that is much simpler, just use track by in your repeat. It will get rid of the $$hashKey attribute that is causing so much grief.
<div ng-repeat="item in items track by $index">{{item.name}}</div>
I'm a bit late to this but I thought I would like to add my two cents as I was shaking my head to all the other answers. Hopefully this can help you to avoid this issue all together.
Use the angularFire library intended to handle angular data and use it's methods.
while yes you can use the pure javascript library methods to .push() .add() .update(), .set() ect.
So if you want to avoid any clashes when firebase communicates with angular javascript you need to be using the appropriate .$foo() methods (i.e. .$save()). In your case just add the $ to your .add() (make it .$add())
so use lessondata.$add($scope.topic);
differences when saving with firebase's vs angularfire's
AngularFire's $save() method is implemented using Firebase's set() method.
Firebase's push() operation corresponds to AngularFire's $add() method
Typically you should be using set()/$save() if you either have an object that already exists in the database or if you are working with objects that have a natural key.
more info on that here: https://stackoverflow.com/a/35959496/4642530
Things to note with AngularFire
For a callback:
and if you want a callback to know if your data saved correctly you can do something like this:
var list = $firebaseArray(ref);
list.$add({ foo: "bar" }).then(function(ref) {
var id = ref.key();
console.log("added record with id " + id);
list.$indexFor(id); // returns location in the array
});
I was surprised this wasn't mentioned earlier in any of the other answers, but take a look at these docs https://www.firebase.com/docs/web/libraries/angular/api.html#angularfire-firebasearray-addnewdata
Cheers.
The best way to get rid of the $$hasKeys is to use "track by" in ng-repeats as in the following (as mentioned in the answer above)
<div ng-repeat="(key, value) in myObj track by $index"> ... </div>
but you can also set track as par of ng-model-options
ng-model-options="{ trackBy: '$value.someKeyOnYourObject' }"
on a form control. This way also improves performance of your angular app.
Another way is to remove the $$hashKey is using
angular.copy(someObj);
If all else fails, you could also use lodash to remove the keys that start with the "$".
_.omitBy(yourObject, function(value, key){return _.startsWith(key, '$');});
You can also strip out the property before pushing it.
delete $scope.topic.$$hashKey