Firebase 3 way data binding not working as expected - firebase

I have an AngularJS service that returns a firebase ref.
.factory('sessionManager', ['$firebase','$scope', function($firebase){
var ref=new Firebase('https://telechat.firebaseio.com/sessions');
return $firebase(ref);
}])
In the controller, I have added the dependency and called $bind.
$scope.items=sessionManager;
$scope.items.$bind($scope,'sessions').then(function(unbind){
unbind();
});
But when I print it to the console, the returned data has a collection of functions like $add , $set ,.. etc in addition to the array of data.
Why is this occurring? Am I doing it the wrong way?

If I'm reading the question correctly, you may be under the impression that $bind() is a transformation of the $firebase object into a raw data array or object. In essence, the only difference between a $firebase instance and $bind is that data is local changes are automagically pushed back to Firebase from Angular. Whereas, when you use $firebase without $bind, you need to call $save to push local changes.
Keeping in mind that $firebase is a wrapper on the Firebase API and not a simple data array, you can still treat it like raw data in most cases.
To iterate data in a Firebase object, you can use ngRepeat:
<li ng-repeat="(key, item) in $sessions">{{item|json}}</li>
Or if you want to apply filters that depend on arrays:
<li ng-repeat="(key, item) in $sessions | orderByPriority | filter:searchText">
Or in a controller using $getIndex:
angular.forEach($scope.sessions.$getIndex(), function(key) {
console.log(key, $scope.sessions[key]);
});
The $add/$update/etc methods mentoined are part of the $firebase object's API. The documentation and the tutorial should be great primers for understanding this process.
This is also a part of the API that is continuing to evolve to better match the Angular way of doing things and feedback from users.

Related

Does AngularFire2's database.list hold a reference or actually grab data?

I'm following along the with the basic AngularFire2 docs, and the general format seems to be:
const items = af.database.list('/items');
// to get a key, check the Example app below
items.update('key-of-some-data', { size: newSize });
My confusion is that in the source code, it seems as though calling database.list() grabs all the data at the listed url (line 114 here)
Can anyone help clarify how that works? If it does indeed grab all the data, is there a better way of getting a reference without doing that? Or should I just reference each particular URL individually?
Thanks!
When you create an AngularFire2 list, it holds an internal Firebase ref - accessible via the list's public $ref property.
The list is an Observable - which serves as the interface for reading from the database - and includes some additional methods for writing to the database: push, update and remove.
In the code in your question, you are only calling the update method and are not subscribing to the observable, so no data is loaded from the database into memory:
const items = af.database.list('/items');
// to get a key, check the Example app below
items.update('key-of-some-data', { size: newSize });
It's only when a subscription to the observable is made that listeners for value and the child_... events are added to the ref and the list builds and maintains an internal array that's emitted via the observable. So if you are only calling the methods that write to the database, it won't be loading any data.
The AngularFire2 object is implemented in a similar manner.

AngularFire binding to scope vs $bindTo?

In angular fire documentation, here are the two line I am trying to understand:
// To make the data available in the DOM, assign it to $scope
$scope.data = obj;
// For three-way data bindings, bind it to the scope instead
obj.$bindTo($scope, "data");
Are they doing the same thing? Do both bind to scope for 3 way data binding?
Nope, they're not doing the same thing. That would be a pretty silly API. :-)
Running $scope.data = $firebaseObject(ref) binds the data in the Firebase location to the scope. Any update to the data in the Firebase database, will automatically be reflected in the scope and thus - if you bind an HTML element to $scope.data - also be updated on screen. The flow is uni-directional: from the database, to the screen.
Calling obj.$bindTo($scope, "data") both binds the data in the Firebase location to the scope and listens to changes to $scope.data that it then send back to the Firebase database automatically. So this creates a bi-directional, three-way binding. Updates in the HTML are sent to the database, updates to the database are sent to the screen.
This is all pretty well documented, for example in this blog post "Three-Way Data Binding with Firebase and Angular". The regular AngularFire documentation also contains a good section about three-way data binding.

angularFire $asObject doesn't push

I am new to firebase and trying to use the $asObject as in the angulerFire doc. Basically, I have profile as follows below. I use $asObject to update the email. However when I use $save(), it replaces the entire profile with only the email, rather than pushing it to the end of list ie it works like set() rather than push(). Is how it is meant to work? how do I only push?
Object
{profiles:
{peterpan:
{name:"Peter Trudy", dob:"7th March"}
}
}
My click function:
$scope.angularObject = function(){
var syncProfile = $firebase(ref.child("profiles").child("peterpan"));
var profileObject = syncProfile.$asObject();
profileObject.email= "peter#peterpan.com";
profileObject.$save();
};
You're looking for $update:
syncProfile.$update({ "email": "peter#peterpan.com" });
Note that $update is only available on $firebase and not on the FirebaseObject that you get back from $asObject. The reason for this is that $asObject is really meant as an object that is bound directly to an angular scope. You're not expected to perform updates to it in your own code.
By the way: if the rest of your code is using AngularFire in a similar way, you might consider dropping AngularFire and using Firebase's JavaScript SDK directly. It is much simpler to use, since it doesn't need to mediate between Firebase and Angular's way of working.

Firebase / AngularFire limited object deletes its properties on $save()

I have a primary node in my database called 'questions', when I create a ref to that node and bring it into my project as a $asObject(), I can modify the individual questions and $save() the collection without any problems, however as soon as I try to limit the object, by priority, the $save() deletes everything off of the object!
this works fine:
db.questions = $firebase(fb.questions).$asObject();
// later :
db.questions.$save();
// db.questions is an object with many 'questions', which I can edit and resave as I please
but as soon as I switch my code to this:
db.questions = $firebase(fb.questions.startAt(auth.user.id).endAt(auth.user.id)).$asObject();
// later :
db.questions.$save();
// db.questions is an empty firebase object without any 'questions!'
Is there some limitation to limited objects (pun not intended) and their ability to be changed and saved?? The saving actually saves updates to the questions to the database, but somehow nukes the local $firebase object...
First line of synchronized arrays ($asArray) documentation:
Synchronized arrays should be used for any list of objects that will be sorted, iterated, and which have unique ids.
First line of synchronized objects ($asObject) documentation:
Objects are useful for storing key/value pairs, and singular records that are not used as a collection.
As demonstrated, if you are going to work with a collection and employ limit, it would behoove you to use a tool designed for collections (i.e. $asArray).
If you were to recreate the behavior of $save using the Firebase SDK, it would look like this:
var ref = new Firebase(URL).limit(10);
// ref.set(data); // throws an error!
ref.ref().set(data); // replaces the entire path; same as $save
Thus, the behavior here exactly matches the SDK. You cannot, technically, call set() on a query instance and this doesn't make any sense, really. What does limit(10) mean to a JSON object? If you call set, which 10 unordered keys should be set? There is no correlation here and limit() really only makes sense with a collection of data, not a list of key/value pairs.
Hope that helps.

Use angularFire conjuction with pure firebase js

I am confuse about why we have to use angularFireCollection built-in methods such as add() update() remove() etc instead of using firebase js api directly.
Since we just want to bind the firebase node with angularJS. We can just use
angularFireCollection(nodeRef) then if we want to do something with the binding node.
we can just go back to firebase js api and do something with the node. Example
nodeRef.push() .update() .remove().
To me, this is better than using angularFireCollection().methods(). Because
angularCollection is not completed compare to firebase js api
I don't have to learn / know AGAIN how ngFire works since I alredy learnt in firebase
different methods name confusing me. angularFireCollection().add() vs new Firebase().push()
The answer is that you do not have to use angularFireCollection at all. You can utilize Firebase methods directly. You can also use angularFire or angularFireCollection to sync data down to your client and then push data using Firebase methods directly.
angularFire and angularFireCollection are not a replacement for Firebase, but rather are wrappers intended to abstract away some of the complexities of syncing data to Angular bindings. To get an understanding of what those complexities are, you can simply look at the angularFireCollection code, and see the number of cases that have to be handled.
Consider the following example:
<h1>Hello {{user.name}}</h1>
<input ng-model="user.name" ng-change="change()" />
Without angularFire:
function($scope) {
$scope.user = {};
$scope.change = function() {
new Firebase('URL/users/kato/name').set($scope.user.name);
};
new firebase('URL/users/kato').on('value', function(snap) {
$scope.user = snap.val()||{};
});
}
With angularFire:
function($scope, angularFire) {
angularFire(new Firebase('URL/users/kato'), $scope, 'user');
}
Or, something more sophisticated:
<ul>
<li ng-repeat="user in users">
{{user.name}}
<input ng-model="user.name" ng-change="change(user)" />
</li>
</ul>
Without angularFireCollection, this would be 100+ lines of code (i.e. about the length of the angularFireCollection object) as it needs to handle child_added, child_removed, child_changed, and child_moved events, adding them into an ordered array for Angular's use, looking up keys on each change and modifying or splicing, maybe even moving records in the array. And it also needs to add changes on the client and sync them back to Firebase by finding the correct key for each record.
With angularFireCollection, it can be done in a few lines, like so:
function($scope, angularFireCollection) {
$scope.users = angularFireCollection(new Firebase('URL/users/kato'));
$scope.change = function(user) {
$scope.users.update(user);
};
}
Of course, these abstractions don't cover every use case and there are certainly plenty of places that using Firebase directly is both practical and appropriate.
Hope that helps.

Resources