Reputation: 251
I'm using firebase with a combination of GeoFire, AngularFire and the regular JavaScript API.
In my application, a GeoQuery returns a list of object keys by subscribing to the .on("key_entered")
event. I can then get the objects with the .once()
query method. However, what I really need to do is have a synchronised array which contains the objects who's keys are returned by the GeoQuery. I also need to limit/order these by a their createdAt
timestamp so as not to download every single object. As new objects hit my .on("key_entered")
event, they should be added to the array.
After quite a lot of researching I cannot figure out how I can do this. Is it possible? If not, what would be the best way for me to approach this issue.
Upvotes: 0
Views: 257
Reputation: 61
One possible solution but it is not tested:
/*global angular,Firebase,GeoFire,_*/
(function() {
'use strict';
angular.module('app').provider('GeofireService', function GeofireServiceProvider() {
var firebaseRef = "";
this.setUrl = function(firebaseUrl) {
firebaseRef = firebaseUrl;
};
this.$get = [
'$rootScope',
'$firebaseUtils',
function($rootScope, $firebaseUtils) {
function GeofireService() {
var self = this;
this._firebase = new Firebase(firebaseRef);
this._itemsRef = this._firebase.child('items');
this._geoRef = this._firebase.child('_geofire');
this._geofire = new GeoFire(this._geoRef);
this._list = [];
this._observers = [];
this._sync = new QueryArraySync(this);
$firebaseUtils.getPublicMethods(self, function(fn, key) {
self._list[key] = fn.bind(self);
});
this._sync.init(this._list);
return this._list;
}
GeofireService.prototype = {
add: function(item) {
var itemID = this._itemsRef.push(item);
var thatItem = item;
thatItem.id = itemID.key();
var geofirePromise = this._geofire.set(
thatItem.id, [thatItem.coord.latitude, thatItem.coord.longitude]);
return geofirePromise.then(function() {
return thatItem;
});
},
//Update does not update geofire location, only the item
update: function(id, item) {
var defer = $firebaseUtils.defer();
var itemRef = this._itemsRef.child(id);
//TODO test if undefined or item does not exist
itemRef.update(item, function(err) {
if (err) {
//TODO noooooooooooo
defer.reject(err);
}
defer.resolve(id);
});
return defer.promise;
},
getById: function(id) {
var defer = $firebaseUtils.defer();
var itemRef = this._itemsRef.child(id);
itemRef.on('value', function(snap) {
defer.resolve(snap.val());
});
return defer.promise;
},
getAll: function() {
var defer = $firebaseUtils.defer();
this._itemsRef.on('value', function(snapshot) {
defer.resolve(snapshot.val());
});
return defer.promise;
},
distance: function(loc1, loc2) {
return GeoFire.distance(loc1, loc2);
},
updateQuery: function(queryCriteria) {
//TODO test if sync is destroyed
this._query = this._sync.updateQuery(queryCriteria);
},
stopCallbacks: function() {
_.each(this._observers, function(callback) {
callback.cancel();
});
this._observers = [];
},
on: function(eventType, callback) {
if (this._query !== undefined) {
var cb = this._query.on(eventType, callback);
this._observers.push(cb);
return cb;
}
}
};
function QueryArraySync(geofireArray) {
function init(list) {
_list = list;
}
//TODO remove if not used
function destroy() {
if (!sync.isDestroyed) {
sync.isDestroyed = true;
_.each(_callbackRegister, function(cb) {
cb.cancel();
});
}
}
function onKeyEntered(key, location, distance) {
return geofireArray.getById(key)
.then(function(data) {
var index = _.findIndex(_list, function(item) {
return item.id === key;
});
if (index !== -1) {
return;
}
data.location = location;
data.distance = distance;
_list.push(data);
});
}
function onKeyExited(key) {
var index = _.findIndex(_list, function(item) {
return item.id === key;
});
if (index !== -1) {
_list.splice(index, 1);
}
}
function updateQuery(queryCriteria) {
if (_query !== null) {
_query.updateCriteria(queryCriteria);
} else {
_query = geofireArray._geofire.query(queryCriteria);
_callbackRegister.push(_query.on('key_entered', onKeyEntered));
_callbackRegister.push(_query.on('key_exited', onKeyExited));
}
return _query;
}
var _list;
var _query = null;
var _callbackRegister = [];
var sync = {
init: init,
destroy: destroy,
isDestroyed: false,
updateQuery: updateQuery
};
return sync;
}
return new GeofireService();
}
];
});
})();
With this solution, we can create items with _geofire positions, update them, retrieve all of items and maintain an array of items which are within the query you set up with updateQuery method.
Idea inspired from Frank answer!
Upvotes: 0
Reputation: 598728
Yes, that is possible.
You'll have to build a class similar to $firebaseArray
logic on top of the key_
events ($firebaseArray
uses child_
events).
since the AngularFire project is available on Github, this would be a good place to start: https://github.com/firebase/angularfire/blob/master/src/FirebaseArray.js
Upvotes: 1