1
votes

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 denormalised data coming from two tables in Firebase:

The book table looks like this:

books:
    -JyDpkQrUozE3L7flpo: {
    title: "title1",
    author: {-JyDpkQrUozE3L7fl1x: true}
    },
    -JyDpkQrUozE3L7flp1: {
    title: "title2",
    author: {-JyDptrrrezE3L7flKx: true}
    },
    -JyDpkQrUozE3L7flp2: {
    title: "title3",
    author: {-JyDpkQrUozE3L7f222: true}
    }
    

The author table looks like this:

authors:
    -JyDpkQrUozE3L7flKx: {
        firstname: "jacques",
        lastname: "smith"
    },
    -JyDptrrrezE3L7flKx: {
        firstname: "bill",
        lastname: "halley"
    },
    -JyDpkQrUozE3L7f222: {
        firstname: "john",
        lastname: "ford"
    }
    

My controller looks like this:

.controller('booksController', ['$scope', '$firebaseArray', function($scope, $firebaseArray) {
     var ref = new Firebase("https://[PATH].firebaseio.com/";
     $scope.bookList = $firebaseArray(ref.child('books'));
     $scope.authorList = $firebaseArray(ref.child('authors'));
     $scope.getAuthor = function(id) {
        return $scope.authorList.$getRecord(id);
    };
});
    

And my html looks like this:

<pre>
<p ng-repeat="book in books" ng-init="author = getAuthor(book.author)">
The author of {{book.title}} is {{author.firstName}} {{author.lastName}}.
</p>
</pre>

The desired output of the above should be: "The author of title1 is Jacques Smith. The author of title2 is Bill Halley ...". What I'm getting however is: "The author of title 1 is. The author of title2 is..." So the author in my html returns a blank.

Any idea?

3
"This just does not work." - Where exactly do the errors occur; what have you ruled out; what did you expect the output to be, etc.Grade 'Eh' Bacon
Grade, I updated the question.Stephane B.
/books/<id>/author is an object, but here you've tried to use it as a string/key. Brandon has the right idea here.Kato

3 Answers

1
votes

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.

1
votes

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();
   });
});
0
votes

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>