Accessing nested unique key values in firebase - javascript

I'm sure this is a trivial question but can't seem to figure out how to access the players id in this firebase array. I need to be able to access all the players id's, and if the current users.id established at login matches one of the players id's firebase array then those games will be looped over with ng-repeat. I know how to accomplish the latter, I just can't figure out to access the players id's inside the unique id's; Hopefully that makes sense. Any help is greatly appreciated, thanks.
JS
(this is some of the code associated with my problem)
game.controller('games.controller', ['$scope', '$state', '$stateParams', 'Auth', '$firebaseArray','Fire', function ($scope, $state, $stateParams, auth, $firebaseArray, fire) {
$scope.games = $firebaseArray(fire.child('games'));
$scope.view = 'listView';
$scope.setCurrentGame = function(game) {
$scope.currentGame = game;
};
$scope.createGame = function() {
if ($scope.format == 'Match Play') {
$scope.skinAmount = 'DOES NOT APPLY';
$scope.birdieAmount = 'DOES NOT APPLY';
}
$scope.games.$add({
name: $scope.gameName,
host: $scope.user.name,
date: $scope.gameDate,
location: {
course: $scope.courseName,
address: $scope.courseAddress
},
rules: {
amount: $scope.gameAmount,
perSkin: $scope.skinAmount,
perBirdie: $scope.birdieAmount,
format: $scope.format,
holes : $scope.holes,
time: $scope.time
}
})
$state.go('games');
};
$scope.addPlayer = function(game) {
$firebaseArray(fire.child('games').child(game.$id).child('players')).$add({
id : $scope.user.id,
name : $scope.user.name,
email : $scope.user.email
});
}
// swap DOM structure in games state
$scope.changeView = function(view){
$scope.view = view;
}
}]);

You're violating two common Firebase rules here:
if an entity has a natural, unique id, store it with that id as its key
don't nest lists
I'm guessing #1 happened because you are storing the players using $firebaseArray.$add(). Instead of repeating myself, I'll list a few questions that deal with the same problem:
Firebase make user object from auth data
Adding users to firebase
How to bypass unique ID and reference child nodes
The nesting of lists is a common mistake. By having the players stored under each game, you can never load the data for a list of games, without also loading all players for all games. For this reason (and others) it is often better to store the nested list under its own top-level:
games
-Juasdui9
date:
host:
-Jviuo732
date:
host:
games_players
-Juasdui9
-J43as437y239
id: "Simplelogin:4"
name: "Anthony"
-Jviuo732
....
users

Related

AngularJS UI-Router / $stateParams - Get data based on object ID

I have an app that directs to a custom url based on a given employeeId parameter when a particular employee is clicked on in a list. When the employee is clicked on, you are taken to an employee details page with their id property as a parameter, and I can display this property in the dom.
What I'm trying to do is to display this employee object's other properties in this different state, which I've had a look around at trying to do, but can't find a solution that matches what I'm trying to do.
Example:
Clicking on employee number 21101994 on employees/employeesList state directs to employees/employeeDetails/?21101994 page and displays only their data in the js fields such as {{employee.firstName}}.
I can successfully show the id, but I want to be able to show ALL of the data for the object that matches the employee's id.
The url routing is working fine, and clicking on this employee on the list page directs correctly to a details page with their parameter, but I can't seem to successfully pass their data into the new state/controller.
-
HTML link:
<li class="collection-item col-xs-12" data-ng-repeat="employee in employees | orderBy: sortByAllDepartments | filter:searchAllDepartments">
<a ui-sref="employees/employeeDetails({employeeId: employee.id})" class="employeeLink"></a>
-
What I've tried with the states:
.state('employees/employeesList', {
url: '/employees/employeesList',
templateUrl: 'pages/employees/employeesList.html',
controller: 'employeesListController'
})
.state('employees/employeeDetails', {
url: '/employees/employeeDetails/:employeeId',
templateUrl: 'pages/employees/employeeDetails.html',
resolve: {
employeeId: function($stateParams) {
return $stateParams.employeeId;
}
},
controller: function(employeeId) {
console.log(employeeId)
}
})
-
Employees service:
app.service('employeesService', function() {
var employees = [
{
id: '21101994',
firstName: 'Employee',
lastName: 'One'
}
];
var addEmployee = function(newObj) {
employees.push(newObj);
};
var getEmployees = function() {
return employees;
}
return {
addEmployee: addEmployee,
getEmployees: getEmployees
}
})
-
Employees List Controller:
app.controller('employeesListController', function($scope, $stateParams, employeesService) {
$scope.active = 'active';
$scope.sortByAllDepartments = '+lastName';
$scope.employees = employeesService.getEmployees();
})
The states will be receiving params like
url: '/employees/employeeDetails/{:employeeId}',
Or
url: '',
params: {
employeeId: null
},
resolve: {...}
I prefer the second one to receive due to clarity.
To pass all the data of employee, localstorage of employee object will be better in case you need this object frequently or in other parts in application.
localStorage.setItem('employeeData', JSON.stringify(empData));
To access this you can do
let empData = JSON.parse(localstorage.employeeData); //Object
In case you need to need this stored data, let's say you are navigating away from this employeeDetails state, you can delete this
localstorage.removeitem('employeeData');
If you require passing multiple state params, just add a comma separated string
ui-sref="employeeDetails({id: emp_id, name: emp_name, mobile: emp_mobile})"
and then receive this in state as below
params: {
employeeId: null,
employeeName: null,
employeeMobile: null
},
I hope this last makes clear as in why should you avoid passing too many params in stateParams

Modelling a Chat like Application in Firebase

I have a Firebase database structuring question. My scenario is close to a chat application. Here are the specifics
- users(node storing several users of the app)
- id1
name: John
- id2
name: Meg
- id2
name: Kelly
- messages(node storing messages between two users)
- message1
from: id1
to: id2
text: ''
- message2
from: id3
to: id1
text: ''
Now imagine building a conversations view for an individual user. So I want to fetch all messages from that particular user
and to that particular user
I am writing it as follows right now:
let fromMessagesRef = firebase.database().ref('messages').orderByChild('from').equalTo(firebase.auth().currentUser.uid)
fromMessagesRef.once("value").then((snapshot) => {/* do something here*/})
let toMessagesRef = firebase.database().ref('messages').orderByChild('to').equalTo(firebase.auth().currentUser.uid)
toMessagesRef.once("value").then((snapshot) => {/* do something here*/})
Questions:
Is this the right way to model the problem?
If yes, is there a way to combine the above 2 queries?
I would store the data like this:
- users(node storing several users of the app)
- id1
name: John
messages
message1: true
message2: true
- id2
name: Meg
messages
message1: true
message3: true
- id3
name: Kelly
messages
message2: true
message3:true
- messages(node storing messages between two users)
- message1
from: id1
to: id2
text: ''
- message2
from: id3
to: id1
text: ''
- message3
from: id2
to: id3
text: ''
Firebase recommends storing things like this. So in your case your query would be
let fromMessagesRef = firebase.database().child('users').child(firebase.auth().currentUser.uid).child('messages')
This allows it to be very fast as there is no orderBy being done. Then you would loop over each message and get it's profile from the messages node.
The structure you have is one possible way to model this data. If you're building an application like this, I would highly recommend the angularfire-slack tutorial. One potentially faster way to model the data would be to model the data like is suggested in this tutorial https://thinkster.io/angularfire-slack-tutorial#creating-direct-messages
{
"userMessages": {
"user:id1": {
"user:id2": {
"messageId1": {
"from": "simplelogin:1",
"body": "Hello!",
"timestamp": Firebase.ServerValue.TIMESTAMP
},
"messageId2": {
"from": "simplelogin:2",
"body": "Hey!",
"timestamp": Firebase.ServerValue.TIMESTAMP
}
}
}
}
}
The one thing you need to watch for in this case if you choose to do it like this is that before your query, you need to sort which user will be the "primary user" under whom the messages will be stored. As long as you make sure that is the same every time, you should be good to go.
One improvement you could make to this structure is something you already pointed out - flattening your data and moving the messages to another node - like you did in your example.
To answer your second question, if you were to keep that structure, I think you would need both of those queries, because firebase does not support a more complicated OR query that would allow you to search both at the same time.
No. Firebase Auth subsystem is where you want to store the email, displayName, password, and photoURL for each user. The function below is how you do it for a password-based user. oAuth-based users are easier. If you have other properties you want to store, like age for example, put those under a users node with each users uid that Firebase Authentication provides you.
function registerPasswordUser(email,displayName,password,photoURL){
var user = null;
//NULLIFY EMPTY ARGUMENTS
for (var i = 0; i < arguments.length; i++) {
arguments[i] = arguments[i] ? arguments[i] : null;
}
auth.createUserWithEmailAndPassword(email, password)
.then(function () {
user = auth.currentUser;
user.sendEmailVerification();
})
.then(function () {
user.updateProfile({
displayName: displayName,
photoURL: photoURL
});
})
.catch(function(error) {
console.log(error.message);
});
console.log('Validation link was sent to ' + email + '.');
}
As for the messages node, get the random id from Firebase Realtime Database's push method and use that as the id of each message under messages. Firebase queries are used:
var messages = firebase.database().ref('messages');
var messages-from-user = messages.orderByChild('from').equalTo('<your users uid>');
var messages-to-user = messages.orderByChild('to').equalTo('<your users uid>');
messages-from-user.once('value', function(snapshot) {
console.log('A message from <your users uid> does '+(snapshot.exists()?'':'not ')+' exist')
});
messages-to-user.once('value', function(snapshot) {
console.log('A message to <your users uid> does '+(snapshot.exists()?'':'not ')+' exist')
});
Define an index for messages-from-user and messages-to-user in your Rules:
{
"rules": {
"messages": {
".indexOn": ["from", "to"]
}
}
}
Below data structure gives you more flexibility with you data. Instead of having to store each messages that user sent back and forth I would suggest to store it in separate node and store the messageID with each user that is involved in the conversation.
Obviously you need to set the security rules, so other user can't see conversation if they are not in the conversation.
By doing this we are not creating deep chain node inside user info
- users(node storing several users of the app)
- id1
name: John
messages: [msID1, msID9]
- id2
name: Meg
messages: [msID1, msID7]
- id3
name: Kelly
messages: [msID9, msID7]
- messages(node storing messages between two users)
- msID1
from: id1
to: id2
text: ''
- msID7
from: id3
to: id2
text: ''
- msID9
from: id3
to: id1
text: ''
Firebase has actually built a demo (and extendible) chat application called Firechat. The source and documentation is provided, and of particular note is the section on their data structures.
Although they've implemented chatrooms, you can see that they've flattened their data structures as in many of the other answers. You can read more about how and why this is done in the Firebase guide.

Meteor User table value axtracting

How do I pick the email address value from meteor Mongo user table?
I have written below query to pick the element:
users=Meteor.users.find({},{emails:1})
This the code I have written to fetch the email address, but I don't know how much it's affecting performance in the code:
users = Meteor.users.find({})
users.forEach(function(key,option){
key.emails.forEach(function (key,option){
console.log(key.address)
});
});
In meteor, you should call:
users = Meteor.users.find({}, { fields: { emails: 1 } })
Reference in docs
EDIT
Please remember users is a cursor object. Cursor objects can be handled directly in templates, and must be the return of publications. You can't iterate a cursor directly in a javascript loop.
Example: (remember authorization in production publications)
Meteor.publish('user-emails', function() {
return Meteor.users.find({}, { fields: { emails: 1 } });
});
If you want to directly access the user instances, for example to iterate them in a javascript code, you need to fetch the cursor (reference in docs).
Example:
var users = Meteor.users.find({}, { fields: { emails: 1 } }).fetch();
Now users is an array of users. Feel free to iterate them.
Example (I'm using underscore.js):
var users = Meteor.users.find({}, { fields: { emails: 1 } }).fetch();
_.each(users, function(user) {
console.log(user.emails);
});
Now, if you need a vector only with emails, one on each index, you can pluck the emails from a fetched array with underscore.js (reference of pluck)
var emails = _.pluck(Meteor.users.find({}, { fields: { emails: 1 } }).fetch(), 'emails');
Hope it works :)
if its not working, dont forget to return
return users

AngularJS localstorage for a factory

I am a newbie to IonicFrameWork and was following their "starter tab" template and made a few modifications to "delete" and "bookmark" items from a factory.
My books.js which contains the factory looks as follow:
.factory('Books', function() {
// books data
var books = [{
id: 0,
title: 'Sample Title',
author: 'Sample Author',
category: 'Horor, Fiction',
cover: '/cover.jpeg',
details: 'some details about the book',
chapters: [
{
id : 1,
name: 'Chapter 1',
filename: 'chapter1.html',
},
{
id : 2,
name: 'Chapter 2',
filename: 'Chapter2.html',
}
]
}
.....
return {
all: function() {
return books;
},
// remove a book from the list
remove: function(book) {
books.splice(books.indexOf(book), 1);
},
and my controllers.js looks like this:
....
.controller('DashCtrl', function($scope, Books) {
$scope.books = Books.all();
$scope.remove = function(book) {
Books.remove(book);
};
})
.controller('singlebookCtrl', function($scope, $stateParams, Books){
$scope.book = Books.get($stateParams.bookId);
$scope.toggleIcon = function ($evemt, iconName, book){
var buttonClasses = $event.currentTarget.className;
// add the book to favorite
if (....){
book.isFavorite = true;
}
// remove the book from favorite
else {
book.isFavorite = false;
}
....
when I exit the app and open it again, the deleted item is back and favorite items are gone.
When searching for a solution , I came across this article which states I should use window.localstorage. But not sure how I should apply this method for a factory.
I personnaly prefer using ngStorage that makes it very simple and straight forward to use localStorage & sessionStorage.
For example, after injecting the dependency in your controller you can:
Set a variable :
$scope.favList = [1, 4, ...]
$scope.jsonList = { ... }
$localStorage.favLists = $scope.favList;
$localStorage.jsonList = $scope.jsonList;
Access a variable, Simply access to localStorage value :
var favList = $localStorage.favLists;
For all intents and purposes you can treat Local Storage just as if it were a key/value store, like a javascript object. So if you want to save a value in local storage, just do it like the following.
window.localStorage["bookOne"] = "STRING HERE"
Or if you want to save a javascript object:
window.localStorage["bookOne"] = JSON.stringify({a:b})
And it should persist between page reloads.
The real issue here is that in your code, you are setting books on each load with var books = .... Every time you reload the application it will re-apply books and favourites will be lost. So beyond just saving it to window.localStorage you will also have to read from local storage and assign it to your books and favourites variables when your app loads in order to see the changes that were previously made.
You can simply do it with angular-local-storage module, here's some example based on your problem.
angular.module('app', ['LocalStorageModule'])
.factory('Books', function(localStorageService) {
// favorites list(books id)
var favList = [1, 2, 3, ...];
// ....
return {
remove: function(id) {
favList.splice(favList.indexOf(id), 1);
// sync with localStorage
localStorageService.set(favorites, favList);
},
// ....
}
});
Note that you can simply use angular-local-storage#bind and bind specific scope-key to this service that automatically do this synchronisation for you. for example:
// Inside your controller
$scope.favList = [1, 4, ...]
// returns a deregistration function for this listener.
$scope.unbind = localStorageService.bind($scope, 'favList');

AngularJS Nested Object Array Pathway

I have a factory, which goes into a controller, and I am trying to get data from that display on an HTML page. I am having trouble specifying an Object's pathway however.
My Factory:
app.factory('APIMethodService', function() {
var Head = "api.example.com";
return {
apis:
[{
accounts: [
{
v1: [
{
uri: Head+"/v1/accounts/",
item1: "AccountNumber",
item2: "MoneyInAccount"
}],
v2: [
{
uri: Head+"/v2/accounts/",
item1: "AccountNumber",
item2: "MoneyInAccount"
}]
}
],
customers: [
{
v1: [
{
uri: Head+"/v1/customers/",
item1: "CustomerName",
item2: "CustomerID",
item3: "CustomerEmail"
}]
}
]
}]
};
});
My Controller:
app.controller('APIController', function($scope, APIMethodService) {
$scope.title = "API";
$scope.apiList = APIMethodService;
$scope.accountList = $scope.apiList.accounts.v1;
$scope.accountList2 = $scope.apiList[0][0];
});
My HTML
<div ng-controller="APIController">
<div id="api" class="row">
<div class="col-xs-12">
<div class="row" style="font-size:20px">
{{title}} Page!
<table class="table table-striped">
<tr ng-repeat="api in apiList | orderBy:'uri' | filter:search">
<td>{{api.uri}}</td>
<td>{{api.item1}}</td>
<td>{{api.item2}}</td>
</tr>
</table>
</div>
</div>
</div>
</div>
The errors I get are in regards to the Controller trying to parse out the individual objects I wish to grab, like accounts or customers, and then any version v#, they may have.
So it will say something such as
TypeError: Cannot read property 'v1' of undefined
I just need some help specifying the proper pathways into my factory service.
You have a few problems. First, you are referring to the object returned from the factory incorrectly. APIMethodService is the factory that you're injecting, so you need to first reference the object that that factory is returning like this:
APIMethodService.apis
This will give you your entire JSON object.
From there, the rest of your object is made up of arrays of objects, so referring to 'v1' won't do you any good. You need to specify an index instead. If you want v1, you'll need:
APIMethodService.apis[0].accounts[0].v1
This will give you the v1 array, which again is an array of objects.
Customers would be:
APIMethodService.apis[0].customers[0].v1
The first problem you have is that the factory returns an object with a single property called apis. So basically this $scope.apiList.accounts.v1 should be $scope.apiList.apis.accounts.v1. Bu that's not all as this won't either work since dotting(.) into apis is an array you'd have to use the index. In this case it would be $scope.apiList.apis[0] and then you could .accounts[0].v1 which is also an array containing a single object.
Now if you can I would suggest to you that you'd change how you represent this data structure.
This is how you could do it.
app.factory('APIMethodService', function() {
var Head = "api.example.com";
return {
accounts: {
v1: {
uri: Head+"/v1/accounts/",
items: ["AccountNumber","MoneyInAccount"]
},
v2: {
... // skipped for brevity
}
},
customer: {
... // code skipped for brevity
}
};
});
And then it's just a matter of dotting into your APIMethodService-object like APIMethodService.accounts.v1.items[0] if you want the AccountNumber method name.
Constructing your url could then be done like this.
var baseUrl = APIMethodService.accounts.v1.uri; // 'api.example.com'
var url = baseUrl + APIMethodService.accounts.v1.items[0]; // 'AccountNumber'
// url = "api.example.com/v1/accounts/AccountNumber"
Again, this is one way you could do it but this can be further enhanced upon. The examples I provided are simply for demo purposes and this is not in any way the only way to do it.
Expanding upon recieved comments/questions your service (and data representation) could now look like this.
app.factory('APIMethodService', function() {
var Head = "api.example.com";
return {
accounts: {
v1: {
uri: Head+"/v1/accounts/",
items: [
{
name:'AccountNumber',
description:'Show the account number'
},
{
name:'AccountOwner',
description:'Show information about the owner of the account'
},
{
name:'MoneyInAccount',
description:'Show money in the Account'
}
]
},
v2: {
... // skipped for brevity
}
},
customer: {
... // code skipped for brevity
}
};
});
// Get descriptions
var accountNumberDescription = APIMethodService.accounts.v1.items[0].description; // 'Show the account number'
var accountOwnerDescription = APIMethodService.accounts.v1.items[1].description; // 'Show information about the owner of the account'
var moneyInAccountDescription = APIMethodService.accounts.v1.items[2].description; // 'Show money in the Account'
By using objects with properties like this it's alot easier to understand what you are trying to do. With arrays with indexes you'd have to know or take a look at the source to see what's going on. Here, someone viewing your code they can instantly understand that it is the description you are getting.

Categories

Resources