Hello fellow Angular gurus!
I have a simple website with some galleries. The gallery names and content are saved in Firebase along with few other data.
I would like to synchronize the whole tree with Angular to allow admins to edit anything on the page.
This is how I sync it and it mostly works ...
var ref = new Firebase("https://profile-web.firebaseio.com/");
// create an AngularFire reference to the data
var sync = $firebase(ref);
// download the data into a local object
var syncObject = sync.$asObject();
// synchronize the object with a three-way data binding
syncObject.$bindTo($scope, "data");
... mostly.
What doesn't work is updating data. I think it's because the tree contains objects with arrays as children. AngularFire requires to use $asArray() to work with arrays. It filters out $$hashkey that gets added by Angular's ng-repeat and breaks the update process. $asObject() doesn't do that.
I tried binding the whole tree with $asArray() but since it's not an array it fails.
So my question: How do I sync the whole tree that is an object with arrays as children that have arrays as children themselves?
I have a working copy here
http://tr.tomasreichmann.cz/
Your time and effort is greatly appreciated. Thank you!
Related
Am in the angular-fire-seed tutorial stage and experimenting with messages and child posts, for some bizarre reason I cannot see the children when I explicitly try to display them, but can see it when expanding on the parent node in the console. These messages display properly in the html where I have the ng-repeat, so I know I am getting the messages at least, albeit childless.
I thought angularfire-seed utils might be chopping or slicing some children so I reverted to straight firebase
This is what I have:
Code:
-----
var url = fbutil.ref() + "/messages/";
var ref = new Firebase(url);
var sync = $firebase(ref).$asArray();
console.log(sync); //this I can see as a proper $firebaseArray object
console.log(sync.length); //this displays as 0 even though length is 3 in object above
console.log(sync[1]); //displays as undefined
Data:
----
messages/id1/text
/id2/text
/id2/post
/id3/text
Thanks in advance for pointing out what I am mis-assuming !
You seem to be falling for the common asynchonisity problem.
This is your code fragment:
var sync = $firebase(ref).$asArray();
console.log(sync); //this I can see as a proper $firebaseArray object
console.log(sync.length); //this displays as 0 even though length is 3 in object above
console.log(sync[1]); //displays as undefined
The call to $asArray() doesn't return a Firebase array immediately, since the data may take some time to come down from the Firebase servers. So instead it returns the promise of a Firebase array, something that in the future will contain your data.
By the time you inspect the out of console.log(sync) in the console, the data will have downloaded so it is (as you say) a proper synchronized array. But when the console.log(sync.length) line runs, the data probably hasn't downloaded yet.
You can wait for the data to be downloaded with the $loaded() method. So:
var sync = $firebase(ref).$asArray();
console.log(sync);
sync.$loaded().then(function(sync) {
console.log(sync.length);
console.log(sync[1]);
}
got the answer from AngularFire Accessing child element methods
short answer: once assigned to a JS var, it is a POJO, not a $firebase object anymore.
I'm following the "denormalized" data pattern that #Anant described nicely on the Firebase blog. To query and render child objects he suggests listening for child_added events on the parent to get the child ids, then querying those children individually with .once('value', fnc) to render them. From the blog post (he uses a posted Link as the parent and Comments as children -- think Reddit or Hacker News):
var commentsRef =
new Firebase("https://awesome.firebaseio-demo.com/comments");
var linkRef =
new Firebase("https://awesome.firebaseio-demo.com/links");
var linkCommentsRef = linkRef.child(LINK_ID);
linkCommentsRef.on("child_added", function(snap) {
commentsRef.child(snap.name()).once("value", function() {
// Render the comment on the link page.
));
});
[sic]
I'm trying to reconcile this with my AngularJS view, since adding the result of .once into a $scope array and using ngRepeat will leave you with a static list (the children won't update in realtime if they are changed or removed by another client).
Put another way, I'd like to have something like an angularFireCollection of child objects that will add, remove and update dynamically.
I think what you're looking for is a combination of AngularFire and FirebaseIndex. I haven't checked that combining these still works, but Kato reports it has in the past.
Ignoring that for a second, though, I don't see anything wrong with your proposed plan:
push the result of .once into an array, add it to the $scope and use ngRepeat
In this case, Kato's FirebaseIndex will probably still be useful, so definitely check that out.
I have AngularJS application that use $resource service to retrieve data using query() method and create new data using model.$save() method. This works fine exactly as the docs say it should.
My question is how to update my local data fetched using MyService.query() in the first place after I've changed it?
I took the most simple approach for now and I simply call the query() method again. I know this is the worst way efficiency-wise but it's the simplest one.
In my server-side I return the whole state-representation of the new model. How can I add the newly created model to the array of the local data?
UPDATE
I've end up simply pushing the model return from the server but I'll still be happy to know if that's the way to go. From what I can understand from the source code the return array is plan-old-javascript-array that I can manipulate myself.
This is the code I used
$scope.save = function () {
var newComment = new CommentsDataSource();
newComment.Content = $scope.todoText;
newComment.$save({ id: "1" }, function (savedComment) {
$scope.comments.push(savedComment);
});
}
I would simply get the whole list again, to be able to see the modifications brought to the list by other users.
But if the solution you're using suits you, then use it. It's corrrect. Angular uses bare-bones JavaScript objects. Adding a new instance to a list in the scope will refresh the list displayed on the page.
So what I'm wanting to do is have Backbone fetch all my collections when the router starts, and then keep all the collections and not have to re-fetch and reload all the collections while moving to different routes in the router. Does anyone know a way to do this?
From Backbone.js docs:
Note that fetch should not be used to populate collections on page
load — all models needed at load time should already be bootstrapped
in to place. fetch is intended for lazily-loading models for
interfaces that are not needed immediately: for example, documents
with collections of notes that may be toggled open and closed.
This is what I meant in my comment:
<script>
define("data", function() {
return <?php echo json here ?>;
});
</script>
Then you can have var data = require("data"); and use it to init Backbone models/collections. I'm not sure this is the right way of doing it.
Well, backbone does this by default. Just add code to your router to create an instance of each collection and call fetch() once on each collection. Then make sure the rest of your app just uses those same collection instances and does NOT call fetch() on them again. It's really that simple.
However, I presume you want other bits of your application to be able to call fetch() and have this silently use cached data if needed. This a considered a hard problem to do correctly, but a naive and simple implementation would be to simply store an isCached flag as a property of your collection and check that in your overridden fetch() method and just return without doing anything if your collection data is already loaded.
Currently I'm doing a local Backbone app. And I want to know how to save data, specifically, how to construct the url attribute for Collection and Model. I have created a folder called data which is intended to hold the data. But how is the data structured? Is it just a single json file to hold the whole Collection data in it? Or it has many seprate json files to hold each Model's data individually? If it's a single json file, how should I created the url atrributes for both Collection and Model? If they are many separate json files, What should I do?
It is upto you :) . Backbone.Js models, collections work and can be made to work with pretty much any type of data source that expose through HTTP and URL. But it was designed to work against REST based services out of the box. Since you are interested to test the library and learning it i will advice you to stick to static JSON file or even Twitter timeline service.
Since all collections are in single JSOn file you will need just to set the URl for the collection.
View that renders the collection will instantiate the models and render them from the collection
Application
window["Application"] = {};
Application.Model = {};
Application.Collection = {};
Application.Views = {};
Application.Templates = {};
Application.Router = {};
Collection from url
Application.Collection.TimeLine = Backbone.Collection.extend({
url:"data/collection.json"
});
Current Url is http://localhost/timelineapp/index.html
Collection.Fetch will make GET request to http://localhost/timelineapp/data/collection.json
is this not clear enough i will add more detail