At the moment, I'm learning Firebase with AngularFire and I have one problem where I'm stuck.
I'd like to query two list from Firebase one is a public list of messages (called messages
in db) that every authenticated user can get and another one that only the creator can get (private messages users_private/{uid}/messages
). The restriction for private messages is done with Firebase rules.
Getting both arrays with $firebaseArray()
is working but how can I combine them so I can use them in one ng-repeat
? The problem is that the synching with Firebase have to work ($add
and $remove
methods) but it doesn't. If I've found something like array.concat(userArray)
the synch. is broken.
I could probably do two ng-repeat
s and duplicate my HTML markup, but I'd like to have it in one place to keep it DRY. Later filtering would be also difficult with two list e.g. filter by date. The model scheme of private and public messages are identical. The objects only differ in the visibility property private
or public
.
Users
collection is only storing the name of the user and visible in the browser console for any authenticated user. That's required so I can get the user name with it's uid. So these data are not private that's why I've added users_private
with other restrictions.
Why two arrays?
I'd like to keep the private data only visible for the creator. Right now, it is a chat app but later you can think of it as public posts and private posts that only the author can see.
First I've tried to do it with a property visibility
in messages
collection but then I've noticed that you can't restrict the access to it with firebase rules. See rules are not filters
The data structure looks like this (messages, users, users_private are top level documents):
If you have another idea how to create private and public messages any ideas are welcome. But I think I'm on the right track.
My code is based on the angularFire seed project.
The view where I'd like to list the private messages too, looks like this:
Here is my controller where I'd like to combine the arrays (some details to the code: users
is a resolved value from ng-route, the user is also available at $rootScope.user
and the currently logged in user and userSvc
is used here to get the name of a user from its uid
) and the markup snippet of `ng-repeat´:
(function(angular) {
"use strict";
var app = angular.module('myApp.chat', [
'ngRoute', 'firebase.utils', 'firebase', 'angularSpinner'
]);
app.controller('ChatCtrl', ['$scope',
'messageList', 'privateMessageList', 'user', 'userSvc', '$q',
function($scope, messageList, privateMessageList, user, userSvc, $q) {
$scope.messages = [];
$scope.user = user;
$scope.getDisplayName = userSvc.getDisplayName;
// messageList security set with firebase rules
messageList.$loaded().then(function() {
if (user) {
$scope.messages = messageList; //comibinedlist;
privateMessageList.$loaded().then(function(list) {
$scope.showSpinner = false;
$scope.privateMessages = privateMessageList;
//$scope.displayMessages = [messageList,
// privateMessageList];
//updateDisplayMessages(messageList, privateMessages);
console.log('messageList', messageList, 'private list', privateMessageList, list);
});
}
});
$scope.addMessage = function(newMessage) {
var dataList; // storage users/messages for private or /messages
if (newMessage) {
if (newMessage.visibility === 'private') {
dataList = $scope.privateMessages;
console.log('added', dataList);
//angular.extend($scope.messages, $scope.privateMessages);
} else {
dataList = $scope.messages; //$scope.messages.$add(newMessage);
}
// add a timestamp
angular.extend(newMessage, {
timestamp: Firebase.ServerValue.TIMESTAMP
});
dataList.$add(newMessage);
}
};
$scope.removeMessage = function(msg) {
var dataList;
if (msg.visibility === 'private') {
dataList = $scope.privateMessages;
} else {
dataList = $scope.messages;
}
$scope.displayMessages.$remove(msg).then(function(ref) {
console.log('removed', msg, ref.key() === msg.$id, ref.key(), msg.$id); // true
});
// $scope.messages.$remove(msg).then(function(ref) {
// console.log('removed', msg, ref.key() === msg.$id, ref.key(), msg.$id); // true
// });
};
}
]);
app.factory('privateMessageList', [
'fbutil', '$firebaseArray', '$rootScope',
function(fbutil, $firebaseArray, $rootScope) {
var ref = fbutil.ref('users_private', $rootScope.user.uid, 'messages')
.limitToLast(10);
console.log('privMsg user', $rootScope.user.uid);
return $firebaseArray(ref);
}
]);
app.factory('messageList', ['fbutil', '$firebaseArray',
function(fbutil, $firebaseArray) {
var ref = fbutil.ref('messages').limitToLast(10);
return $firebaseArray(ref);
}
]);
app.config(['$routeProvider',
function($routeProvider) {
$routeProvider.whenAuthenticated('/chat', {
templateUrl: 'chat/chat.html',
controller: 'ChatCtrl',
//authRequired: true,
// resolve: {
// // forces the page to wait for this promise to resolve before controller is loaded
// // the controller can then inject `user` as a dependency. This could also be done
// // in the controller, but this makes things cleaner (controller doesn't need to worry
// // about auth status or timing of accessing data or displaying elements)
// user: ['userSvc', function(userSvc) {
// return userSvc.getUser();
// }]
// }
});
}
]);
})(angular);
<div class="list-group" id="messages" ng-show="messages.length">
<div class="list-group-item" ng-repeat="message in messages | reverse">
<strong>{{getDisplayName(message.uid) || 'anonymous'}}: </strong>{{message.text}}
<button href="#" class="btn btn-default btn-xs" ng-click="removeMessage(message)" ng-if="user.uid === message.uid"><i class="fa fa-remove"></i>
</button>
<span class="badge">{{message.visibility}}</span>
</div>
</div>
Here are my current firebase rules (rules for user_private
not checked yet):
{
"rules": {
// todo: add other user lists and only keep the username in users/ --> anyone with auth can get that list
// add users_profile (email etc.), users_roles
// user roles stored separately so we can keep it secure and only accessible (read & write) with uid
"users": {
".read": "auth !== null",
"$user_id": {
//".read": "auth !== null", // && ( auth.uid === $user_id )",
".write": "auth !== null && ( auth.uid === $user_id )"
}
},
"users_private": {
"$user_id": {
".read": "auth !== null", //&& ( auth.uid === $user_id )",
".write": "auth !== null && ( auth.uid === $user_id )"
}
},
"messages": {
".read": "auth != null", //"(data.child('visibility').val() === 'public')",
"$message": {
//".read": true,
/*".read": "(data.child('visibility').val() === 'public') ||
//( auth.uid == data.child('uid').val() ) ||
( root.child('users').child(auth.uid).child('admin').val() === true )", // the world can view public and author can view their message or admins (just for debugging)*/
".write": "(data.child('uid').val() == auth.uid ) || ( auth.uid == newData.child('uid').val() ) ||
(root.child('users').child(auth.uid).child('admin').val() === true)" // only owner or admin can write, user = 10, moderator = 20, admin = 999
}
/*"$uid": {
".write": "auth.uid == $uid" // only owner can write/edit to it if not new
}*/
}
}
}