I am developing an application with Firebase (1.0) and Angular (1.4). The problem I\'m having is to ensure the data in view are synchronised with Firebase, while fetching denorm
I realize this is an old question but I thought I share my solution for those who are still googling.
Like Brandon mentions you shouldn't have the auther as an object, just and ID (reference)
Your data should look like this:
{
books: {
JyDpkQrUozE3L7flpo: {
title: "title1",
authorId: JyDpkQrUozE3L7fl1x
},
JyDpkQrUozE3L7flp1: {
title: "title2",
authorId: JyDptrrrezE3L7flKx
},
JyDpkQrUozE3L7flp2: {
title: "title3",
authorId: JyDpkQrUozE3L7f222
}
},
authors: {
JyDpkQrUozE3L7flKx: {
firstname: "jacques",
lastname: "smith"
},
JyDptrrrezE3L7flKx: {
firstname: "bill",
lastname: "halley"
},
JyDpkQrUozE3L7f222: {
firstname: "john",
lastname: "ford"
}
}
}
Note that I changed the author to authorId to be more explicit what is a authoer object and what is just the id. Now lets get all the books and the author.
app.factory('BooksWithAuthor', function($firebaseArray, $firebaseObject) {
const books = firebase.database().ref('books')
const authors = firebase.database().ref('authors');
const bookList = $firebaseArray.$extend({
$$added: function(snap) {
const book = $firebaseArray.prototype.$$added.call(this, snap);
book.author = $firebaseObject(authors.child(book.authorId));
return record;
}
});
return new bookList(books)
});
app.controller('BookCtrl, function(BooksWithAuthor) {
$scope.BookList = BooksWithAuthor;
});
And then in your HTML just do
<div ng-repeat="book in booklist">
{{ book.title }} was written by {{ book.author.firstname }} {{ book.author.lastname }}
</div>
What I see is that your json data for books has author as an object. This is what is passed into the $getRecord method.The ID of the author is a key, not a value.
I believe if you structure your data like this:
books: {
-JyDpkQrUozE3L7flpo: {
title: "title1",
author: "-JyDpkQrUozE3L7fl1x"
}
-JyDpkQrUozE3L7flp1: {
title: "title2",
author: "-JyDptrrrezE3L7flKx"
},
-JyDpkQrUozE3L7flp2: {
title: "title3",
author: "-JyDpkQrUozE3L7f222"
}
It should work, but it has been a long time since I have used firebase.
Brandon's answer is technically-correct answer to the posed question. I'm going to elaborate a bit on what would be a better way to join these records.
I've actually answered this exact question in a fiddle, and also provided a more sophisticated, elegant, and simpler solution of how to cache and merge user profiles into objects. I'll reiterate the details of how this works here.
app.factory('NormalizedPosts', function($firebaseArray, userCache) {
var PostsWithUsers = $firebaseArray.$extend({
// override $$added to include users
$$added: function(snap) {
// call the super method
var record = $firebaseArray.prototype.$$added.call(this, snap);
userCache.$load( record.user ).$loaded(function( userData ) {
record.userData = userData;
});
// return the modified record
return record;
}
});
return PostsWithUsers;
});
Here I've decided to use a cached list of users, since they are likely to be highly redundant, and this provides an elegant way to keep everything in sync. It's not strictly necessary--we could look them up right there in $$added, but that leaves some edge cases to be handled. So a cache of synchronized users feels right here.
So here's the caching utility:
app.factory('userCache', function ($firebase) {
return function (ref) {
var cachedUsers = {};
// loads one user into the local cache, you do not need to
// wait for this to show it in your view, Angular and Firebase
// will work out the details in the background
cachedUsers.$load = function (id) {
if( !cachedUsers.hasOwnProperty(id) ) {
cachedUsers[id] = $firebaseObject(ref.child(id));
}
return cachedUsers[id];
};
// frees memory and stops listening on user objects
// use this when you switch views in your SPA and no longer
// need this list
cachedUsers.$dispose = function () {
angular.forEach(cachedUsers, function (user) {
user.$destroy();
});
};
// removes one user, note that the user does not have
// to be cached locally for this to work
cachedUsers.$remove = function(id) {
delete cachedUsers[id];
ref.child(id).remove();
};
return cachedUsers;
}
});
And here's a gist putting it all together.
Note that, if we know that when our controller is destroyed, that the data will no longer be useful, we can clean up the listeners and memory by calling $destroy
. This isn't strictly necessary and could be a premature optimization, but is probably worth mentioning for users with complex production apps that have tens of thousands of records to track:
app.controller('...', function(NormalizedPosts, userCache, $scope) {
$scope.posts = new NormalizedPosts(<firebase ref>);
$scope.$on('$destroy', function() {
$scope.posts.$destroy();
userCache.$dispose();
});
});