The gist of this sort of denormalization is to fetch the users as you grab posts. It's nothing more complex than it sounds. Just go grab them.
Firebase does a lot of work internally to optimize requests and re-uses the same socket connection for all the listeners, so this is quite performant--barely more overhead than the amount of bytes being downloaded, regardless of whether they are split into separate paths or stored together.
The HTML:
<h3>Normalizing user profiles into posts</h3>
<ul ng-controller="ctrl">
<li ng-repeat="post in posts | orderByPriority" ng-init="user = users.$load(post.user)">
{{user.name}}: {{post.title}}
</li>
</ul>
The JavaScript:
var app = angular.module('app', ['firebase']);
var fb = new Firebase(URL);
app.controller('ctrl', function ($scope, $firebase, userCache) {
$scope.posts = $firebase(fb.child('posts'));
$scope.users = userCache(fb.child('users'));
});
app.factory('userCache', function ($firebase) {
return function (ref) {
var cachedUsers = {};
cachedUsers.$load = function (id) {
if( !cachedUsers.hasOwnProperty(id) ) {
cachedUsers[id] = $firebase(ref.child(id));
}
return cachedUsers[id];
};
cachedUsers.$dispose = function () {
angular.forEach(cachedUsers, function (user) {
user.$off();
});
};
return cachedUsers;
}
});