angularFire startAt querying and binding deletes new data - firebase

The application shows work-shifts for certain time-period. firebaseConn.getShifts is the API-function to get the shiftData for the given time period.
versions:
firebase: 2.0.6
angularFire: 0.9.0 (confirmed with 0.8.2 also)
This is my firebase schema:
And this is the code:
.factory('watchers', function(bunch-of-dependencies) {
var unbindShifts = function() {};
var inited = false;
var shifts = {};
... some irrelevant code in between ...
function initShifts() {
unbindShifts();
shifts.object = firebaseConn.getShifts( false, from, to, $scope );
$scope.shifts = shifts.object;
shifts.object.$bindTo($scope, "shifts").then(function(unbind) {
unbindShifts = unbind;
});
}
The firebase-queries (that have worked fine before adding the unbind / bind and possibly time-based querying might cause issues too):
firebaseConn.getShifts = function(asArray, from, to, scope) {
return cacheRequest(FBURL + "shifts", asArray, [from, to]);
};
function cacheRequest(url, asArray, limits) {
var type = asArray ? "array" : "object";
var startAt = limits ? limits[0] : undefined;
var endAt = limits ? limits[1] : undefined;
var retObj, FBRef;
cached[url] = cached[url] || {};
/* If there are limits-parameters we don't cache at all atm. Since those queries should be checked differently than static urls */
if(!limits && cached[url][type]) {
FBRef = cached[url][type];
} else {
FBRef = cached[url][type] = createFBRef(url, startAt, endAt);
}
if(asArray) {
retObj = FBRef.$asArray();
} else {
retObj = FBRef.$asObject();
}
return retObj;
}
function createFBRef(resourceURL, startAt, endAt) {
var modifiedObject = $firebase( createRef( resourceURL ).orderByKey().startAt(startAt).endAt(endAt) );
return modifiedObject;
}
function createRef(resourceURL) {
return new Firebase( resourceURL );
}
Now I have located the problem to be with the query limiting. If the from and to Dates are undefined, this works without problems. But I need to be able to limit the amount of data, since loading many years of workshift-data, to show a weeks time, won't be good :).
The actual problem is not displaying and fetching the data, everything works fine, it's related to the times and re-binding.
If I do any changes to e.g. "20150115"-table. For example I add another "groups"-child there. When i unbind and rebind, the whole "20150115"-table gets deleted and this holds true only to the latest changes. If I add multiple child to different dates e.g. "20150113", "20150114", "20150115" and the latest change is in "20150115" and then I unbind + re-bind another time from firebase, all the other root-paths will stay as they are, but the latest change in "20150115" will make the whole tree deleted.
I hope I make myself clear, so for safety I try to explain it again in simpler way.
- Changes to 1. "20150113", 2. "20150114", 3. "20150115" through the app.
- Changing timeline from UI causes: unbind + re-bind
- As a side-effect the whole "20150114" tree gets deleted.
The problem is somehow related to advanced querying with orderByKey().startAt(startAt).endAt(endAt) and binding.
Also for additional info. The data which is added through the UI gets added to the firebase database, but when the re-binding happens, the data is deleted from the database. Specifically on rebind, unbinding causes no issues, if I delay rebinding with timeout.
EDIT:
I have found the source of the actual issue. After the new binding is in place and everything seems to be in order, there is an angular watch event that kicks in. The event tries to save the last change user made before re-binding.
So if I have and active timeline for december (20141201 - 20141230) and I change "20141225"-data. Then change the timeline to 20150101 - 20150130, causing unbind and rebind (or manually fetching new data). There will be an event, after the binding has been done and everything seems to be in order, trying to save 20141225 data to either the new timeline (20150101 - 20150130) or the old one, not sure which one. This causes the firebase to actually delete the whole 20141225-tree, instead of saving the data.

The new data makes it into your Firebase fine, which you can see by either checking your Firebase dashboard or by running a quick snippet like this in your browser's dev console:
new Firebase("https://firebaseurl").once('value', function(s) { console.log(s.val()); })
The data even makes it back into your application. The only problem is that Angular doesn't know that new data has arrived, so it doesn't update the view with the new data.
Normally AngularFire's $asObject and $asArray methods take care of notifying AngularJS when new data arrives from Firebase. But since you are constantly creating new queries, you'll have to take care of that yourself.
There are a few ways to signal the new data to AngularJS and I'm definitely not an expert on which one is best. But if you add $scope.$apply(); to your setDays function it works:
function setDays(ref) {
var FBRange = setFBRange(ref, from, to);
var days;
unbindDays();
days = $firebase(FBRange).$asObject();
$scope.days = days;
days.$bindTo($scope, "days").then(function(unbind) {
unbindDays = unbind;
// As a result of the new binding entry gets mysteriously deleted from firebase
});
$scope.$apply(); // Tell AngularJS about the new data, so that it updates the view
function setFBRange(ref, from, to) {
return ref.orderByKey().startAt(""+from).endAt(from + to + "");
}
}
Updated Plunkr with this change (and some others to help in debugging): http://plnkr.co/edit/YZtkzUNtjQUCcw4xb2mj?p=preview

Related

i need to get the system time & store it as a child value in firebase

I need to add current system time into child data field.
I'm using TypeScript, but this might still give you and idea how you could do it.
My code uses the event.timestamp property to get date and time:
export const onWrite = functions.database.ref('/{databaseName}/{tableName}/{key}').onCreate((event) => {
let ref = event.data.ref;
let isCreate = event.eventType == 'providers/google.firebase.database/eventTypes/ref.create';
ref.once('value').then(e => {
// Cloud functions are sometimes executed with a delay and the record might not exist anymore.
if (e.val() === null) {
return;
}
if (isCreate) {
return ref.update({
'createdDateTime': event.timestamp
});
}
});
});
The created events for clients won't include this added data yet, only a later change event does.
I'm haven't investigated yet if this can be fixed (perhaps by making use of transaction).
I saw your image description and understood u want to add system time into firebase.
If you want to do you can do that by , like below
var fb_db=firebase.database().ref('treeName');
var key=fb_db.push().key;
var updatenode={}
updatenode[key+"\"]= new Date();
fb_db.update(updatenode).then(function(){
alert("Success")
})

Facebook like load new posts in meteor

I'm in the process of learning meteor. I followed the tutorial to create microscope. If some one submits a post meteor will re render the template for all users. This could be very annoying if there are hundreds of posts then the user will come back to the top of the page and loose track of where he was. I want to implement something similar to what facebook has. When a new post is submitted template isn't rendered rather, a button or link will appear. Clicking it will cause the template to re-render and show the new posts.
I was thinking of using observeChanges on the collection to detect any changes and it does stop the page from showing new posts but only way to show them is to reload the page.
Meteor.publish('posts', function(options) {
var self = this, postHandle = null;
var initializing = true;
postHandle = Posts.find({}, options).observeChanges({
added: function(id, post) {
if (initializing){
self.added('posts', id, post);
}
},
changed: function(id, fields) {
self.changed('posts', id, fields);
}
});
self.ready();
initializing = false;
self.onStop(function() { postHandle.stop(); });
});
Is this the right path to take? If yes, how do I alert the user of new posts? Else, what would be a better way to implement this?
Thank you
This is a tricky question but also valuable as it pertains to a design pattern that is applicable in many instances. One of the key aspects is wanting to know that there is new data but not wanting to show it (yet) to the user. We can also assume that when the user does want to see the data, they probably don't want to wait for it to be loaded into the client (just like Facebook). This means that the client still needs to cache the data as it arrives, just not display it immediately.
Therefore, you probably don't want to restrict the data displayed in the publication - because this won't send the data to the client. Rather, you want to send all the (relevant) data to the client and cache it there until it is ready.
The easiest way involves having a timestamp in your data to work from. You can then couple this with a Reactive Variable to only add new documents to your displayed set when that Reactive Variable changes. Something like this (code will probably be in different files):
// Within the template where you want to show your data
Template.myTemplate.onCreated(function() {
var self = this;
var options = null; // Define non-time options
// Subscribe to the data so everything is loaded into the client
// Include relevant options to limit data but exclude timestamps
self.subscribe("posts", options);
// Create and initialise a reactive variable with the current date
self.loadedTime = new ReactiveVar(new Date());
// Create a reactive variable to see when new data is available
// Create an autorun for whenever the subscription changes ready() state
// Ignore the first run as ready() should be false
// Subsequent false values indicate new data is arriving
self.newData = new ReactiveVar(false);
self.autorun(function(computation) {
if(!computation.firstRun) {
if(!self.subscriptionsReady()) {
self.newData.set(true);
}
}
});
});
// Fetch the relevant data from that subscribed (cached) within the client
// Assume this will be within the template helper
// Use the value (get()) of the Reactive Variable
Template.myTemplate.helpers({
displayedPosts = function() {
return Posts.find({timestamp: {$lt: Template.instance().loadedTime.get()}});
},
// Second helper to determine whether or not new data is available
// Can be used in the template to notify the user
newData = function() {
return Template.instance().newData.get();
});
// Update the Reactive Variable to the current time
// Assume this takes place within the template helper
// Assume you have button (or similar) with a "reload" class
Template.myTemplate.events({
'click .reLoad' = function(event, template) {
template.loadedTime.set(new Date());
}
});
I think this is the simplest pattern to cover all of the points you raise. It gets more complicated if you don't have a timestamp, you have multiple subscriptions (then need to use the subscription handles) etc. Hope this helps!
As Duncan said in his answer, ReactiveVar is the way to go. I've actually implemented a simple facebook feed page with meteor where I display the public posts from a certain page. I use infinite scroll to keep adding posts to the bottom of the page and store them in a ReactiveVar. Check the sources on github here and the live demo here. Hope it helps!

AngularFire extending the service issue

I've been looking at the documentation for Synchronized Arrays https://www.firebase.com/docs/web/libraries/angular/api.html#angularfire-extending-the-services and https://www.firebase.com/docs/web/libraries/angular/guide/extending-services.html#section-firebasearray
I'm using Firebase version 2.2.7 and AngularFire version 1.1.2
Using the code below, I'm having trouble recognizing $$removed events.
.factory("ExtendedCourseList", ["$firebaseArray", function($firebaseArray) {
// create a new service based on $firebaseArray
var ExtendedCourseList= $firebaseArray.$extend({
$$added: function(dataSnapshot, prevChild){
var course = dataSnapshot.val();
var course_key = dataSnapshot.key();
console.log("new course");
return course;
},
$$removed: function(snap){
console.log("removed");
return true;
}
});
return function(listRef) {
return new ExtendedCourseList(listRef);
}
}])
.factory("CompanyRefObj", function(CompanyRef) {
//CompanyRef is a constant containing the url string
var ref = new Firebase(CompanyRef);
return ref;
})
.factory('CourseList', function (localstorage,$rootScope,ExtendedCourseList,CompanyRefObj) {
var companyID = localstorage.get("company");
$rootScope.courseList = ExtendedCourseList(CompanyRefObj.child(companyID).child("courses"));
)
If I run this code, only the $$added events will be triggered. To simulate the remove events I use the web-interface at Firebase to display data, where I press the remove button and accept the data being deleted permanently.
Additionally, if I delete the $$removed function, the extended service still won't synchronize when a record is deleted.
If I modify my code to use the $firebaseArray instead of extending the service (as seen above) both add and remove events will be recognized.
.factory('CourseList', function (localstorage,$rootScope,$firebaseArray,CompanyRefObj) {
var companyID = localstorage.get("company");
$rootScope.courseList = $firebaseArray(CompanyRefObj.child(companyID).child("courses"));
)
Finally, are there any bad practices I've missed that can cause some of the extended functions to not work?
Solved
$$added: function(dataSnapshot, prevChild){
var course = dataSnapshot.val();
var course_key = dataSnapshot.key();
//Modified below
course.$id = course_key;
//End of modification
console.log("new course");
return course;
}
After posting about the issue at firebase/angularfire github I received an answer that solved my issue. When $$added got overridden by the code provided, the $firebaseArray also lost its internal record $id.
Adding this line of code: course.$id = course_key; before returning the course, made AngularFire recognize when the record was removed from the server.

how to discard initial data in a Firebase DB

I'm making a simple app that informs a client that other clients clicked a button. I'm storing the clicks in a Firebase (db) using:
db.push({msg:data});
All clients get notified of other user's clicks with an on, such as
db.on('child_added',function(snapshot) {
var msg = snapshot.val().msg;
});
However, when the page first loads I want to discard any existing data on the stack. My strategy is to call db.once() before I define the db.on('child_added',...) in order to get the initial number of children, and then use that to discard that number of calls to db.on('child_added',...).
Unfortunately, though, all of the calls to db.on('child_added',...) are happening before I'm able to get the initial count, so it fails.
How can I effectively and simply discard the initial data?
For larger data sets, Firebase now offers (as of 2.0) some query methods that can make this simpler.
If we add a timestamp field on each record, we can construct a query that only looks at new values. Consider this contrived data:
{
"messages": {
"$messageid": {
"sender": "kato",
"message": "hello world"
"created": 123456 // Firebase.ServerValue.TIMESTAMP
}
}
}
We could find messages only after "now" using something like this:
var ref = new Firebase('https://<your instance>.firebaseio.com/messages');
var queryRef = ref.orderBy('created').startAt(Firebase.ServerValue.TIMESTAMP);
queryRef.on('child_added', function(snap) {
console.log(snap.val());
});
If I understand your question correctly, it sounds like you only want data that has been added since the user visited the page. In Firebase, the behavior you describe is by design, as the data is always changing and there isn't a notion of "old" data vs "new" data.
However, if you only want to display data added after the page has loaded, try ignoring all events prior until the complete set of children has loaded at least once. For example:
var ignoreItems = true;
var ref = new Firebase('https://<your-Firebase>.firebaseio.com');
ref.on('child_added', function(snapshot) {
if (!ignoreItems) {
var msg = snapshot.val().msg;
// do something here
}
});
ref.once('value', function(snapshot) {
ignoreItems = false;
});
The alternative to this approach would be to write your new items with a priority as well, where the priority is Firebase.ServerValue.TIMESTAMP (the current server time), and then use a .startAt(...) query using the current timestamp. However, this is more complex than the approach described above.

Prevent items in scope from writing to a different user's records

I was having success with using AngularFire in a scenario where there is one user on my application.
Now that I have authentication up and running, I'm noticing that assigning items to $scope.items is catastrophic when switching users, mainly due to the $scope failing to update correctly.
Reading directly from the docs...
var ref = new Firebase('https://<my-firebase>.firebaseio.com/items');
angularFire(ref, $scope, 'items');
I need these to be only the items of the currently authorized user. So currently, I do this (if there's a better way, don't hesitate to tell me!)
var ref = new Firebase('https://<my-firebase>.firebaseio.com/items/userId');
angularFire(ref, $scope, 'items');
I generate userId using auth.provider and auth.id, btw. Now that my items are namespaced in (let's say) user1
var ref = new Firebase('https://<my-firebase>.firebaseio.com/items/[user1id]');
angularFire(ref, $scope, 'items');
I add items to $scope.items
$scope.create = function(item) {
$scope.items.push(item)
/* Pretend the user adds these from the interface.
[
{ name: 'eenie' },
{ name: 'meenie' },
{ name: 'miney' },
{ name: 'moe' }
]
*/
}
The problem
Now if I just log out and login as someone else, magically that user has eenie meenie miney and moe because $scope.items held the array between logout and login.
I tried to set $scope.items = [] on logout event, but that actually empties all the records. I'm pulling my hair out. This is 0.001% of what I need to do in my project and it's taking my whole weekend.
Update New method
$scope.create = function() {
$scope.selectedDevice = {
name: 'New Device',
userId: $scope.user.provider + $scope.user.id
};
return $scope.devices.push($scope.selectedDevice);
};
$scope.$on('angularFireAuth:login', function(evt, user) {
var promise, ref;
ref = new Firebase('https://mysite.firebaseio.com/users/' + (user.provider + user.id) + '/registry/');
promise = angularFire(ref, $scope, 'devices');
});
It now will accurately create items under the user's id. However, still, once you logout and log back in, those items do not get cleared from $scope.devices. Therefore, they just add themselves to data but under the newly logged in user.
Update
I did a lot of trial and error. I probably set $scope.devices to [] and moved around login events in every possible combination. What eventually worked was #hiattp's fiddle in the accepted answer.
This is a result of the implicit data binding remaining intact as you switch users. If the new user shows up and creates a new binding, it will consider the existing data to be local changes that it should assimilate (that's why you see the original user's items being added to the new user), but if you try to clear them first without releasing the binding then you are implicitly telling Firebase to delete that data from the original user's item list (also not what you want). So you need to release the data bindings when you detect the logout (or login) events as needed.
The callback in the angularFire promise provides an "unbind" method (see here and here):
var promise = angularFire(ref, $scope, 'items');
promise.then(function(unbind){
// Calling unbind() will disassociate $scope.items from Firebase
// and generally it's useful to add unbind to the $scope for future use.
});
You have a few idiosyncrasies in your code that are likely causing it not to work, and remember that unbind won't clear the local collection for you. But just so you have an idea of how it should work (and to prove it does work) here is a fiddle.
You need to unbind $scope.items on logout. The best way to do this will be to save the unbind function given to your promise in $scope:
var ref = new Firebase('https://<my-firebase>.firebaseio.com/items/[user1id]');
angularFire(ref, $scope, 'items').then(function(unbind) {
$scope.unbindItems = unbind;
});
$scope.$on('angularFireAuth:logout', function() {
$scope.unbindItems();
});

Resources