Reading child of an automated unique ID in a nested ng-repeat - angularjs

How do I get data of a child of an automated unique ID in a nested ng-repeat, if the ID is not in the first level?
My firebase data set is structured as such (note: I do not know the amount or values of users, dates or unique IDs in that data set):
John:[
10-12-2015: {
"-JcFXid1A2G8EM7A_kwc": {
"description" : "hi",
"timestamp": "1449751857810"},
"-JcFZP5FNtL4Yj6nja_7": {
"description" : "this",
"timestamp": "144975185345"},
},
11-12-2015: {
"-JcFtGoZL7J-CCIjTYcL": {
"description" : "is",
"timestamp": "14497518513715"},
"-JcFXid1A2G8EM7A_kwc": {
"description" : "me",
"timestamp": "1449751846956"},
},
]
Thomas: [ ... ]
My HTML code is:
<ion-list>
<ul class="list">
<li ng-repeat="day in list">
<ion-item class="item-divider">{{day.$id}}</ion-item>
// That works as my output is "10-12-2015, etc"
<li ng-repeat="key in day">
// This does not work
<ion-item>{{key.description}}</ion-item>
</li>
</li>
</ul>
</ion-list>
I am new to Firebase and Angular so my apologies if I am missing something obvious. I also understand that Firebase recommends flat hierarchies, but sorting it by user and date is important for data analysis I need to do. But I would be open to suggestions.
FYI - My controller looks like this:
.controller('summaryCtrl', ['$scope','$state','$firebase','$firebaseArray','SessionData', '$ionicPopup', function($scope,$state,$firebase,$firebaseArray,SessionData,$ionicPopup){
var firebaseObj = new Firebase("https://id.firebaseio.com/" + userid);
var fbArray = $firebaseArray(firebaseObj);
fbArray.$loaded()
.then(function(){
$scope.list = fbArray;
})
}])
Thanks.

You don't need to use .$loaded(), the $firebaseArray will take care of triggering $digest when the data is loaded. The .$loaded() promise is useful for resolving in routes.
In your case though you need to go down into the day key to create the array. Try a data structure like below:
{
"userMessages": {
"$uid": {
"$message_id": {
"timestamp": 1450048003760 // use a timestamp
}
}
}
}
This way you can get messages per user by: /userMessages/$uid/. Then using a query you can get them by date.
You can create factory to simplify this.
Don't mind the code structure below, it uses the Angular Styleguide.
angular.module('app', ['firebase'])
.constant('FirebaseUrl', '<my-firebase-app>')
.service('rootRef', ['FirebaseUrl', Firebase])
.factory('messages', Messages)
.controller('SummaryCtrl', SummaryCtrl);
function Messages(rootRef, $firebaseArray) {
return function Messages(userid, date) {
// turn the date into a timestamp
var timestamp = date.getTime();
var messagesRef = rootRef.child('userMessages').child(userid);
// query for messages on that day
var query = messagesRef.orderByChild('timestamp').endAt(timestamp);
return $firebaseArray(query);
};
}
function SummaryCtrl($scope, messages, rootRef) {
var user = rootRef.getUser()
var today = new Date();
$scope.messages = messages(user.uid, today);
}

Related

Accessing Related Object Data in AngularFire for Firebase

I want to relate a business to another business in my Firebase data and then use AngularFire to ng-repeat through the related businesses. My Firebase data is structured like so:
{
"businesses" : {
"-KQ1ggyrnYGYL1084Htz" : {
"name" : "Some Business",
"cat" : "Food",
"related" : {
"-KQ1hFH0qrvKSrymmn9d" : true,
In my JS I have the following factory and controller:
app.factory("Businesses", ['$firebaseArray', function ($firebaseArray) {
var stuff = firebase.database().ref().child("businesses");
var Businesses = $firebaseArray(stuff);
return Businesses;
}])
And my controller:
app.controller('businessCtrl', ['$scope','$firebaseArray','Businesses', function ($scope, $firebaseArray, Businesses) {
$scope.businesses = Businesses;
In the HTML I then loop through the "Related" businesses using an ng-repeat like so:
<span>Related:</span>
<a ng-repeat="item in businesses.related" ui-sref="shop.item({item: item.link})">
<div class="chip">
<img ng-src="{{item.card}}" alt="Contact Person">
{{item.name}}
</div>
</a>
But I seem unable to access any data such as "name" from my {{item.name}}. What do I need to do to be able to access the data of the referenced business?
If you want to access the data item.name then use item in businesses instead of item in businesses.related
When using item in businesses.related you are getting the data as item.-KQ1hFH0qrvKSrymmn9d
Alright, so I discovered how to do this sometime later. It may have worked the way I was trying, but it's not the recommended approach. For one, denormalization in Firebase is normal. Meaning, instead of placing the related businesses in a sub-object I need to create a new object like so:
businesses
-BusinessKey
related
-BusinessKey
-RelatedBusinessKey
Then get the data like so:
$scope.related = [];
const businessKey = $scope.finalItem.$id;
const rootRef = firebase.database().ref();
const discoverRef = rootRef.child('discover');
const businessRef = rootRef.child('businesses');
function getRelatedContent(key, cb) {
relatedRef.child(key).on('child_added', snap => {
let businesRef = businessRef.child(snap.key);
businesRef.once('value', cb);
});
}
getRelatedContent(businessKey, snap => {
var snapVal = snap.val();
$scope.related.push(snapVal);
});

How to get specific element and orderby priority in Firebase and Angular

I have a firebase reference like this:
$scope.lessons = $firebaseArray(firebase.child('Lessons').orderByChild('courseID').equalTo($state.params.courseID));
I'm outputting the lessons like this:
<ul>
<li class="lesson" ng-if="lesson.moduleID == module.$id" ng-repeat="lesson in lessons" lessonid="{{lesson.$id}}">
<lessonedit></lessonedit>
</li>
</ul>
I need to order the lessons by priority, but when I add the orderByPriority() function to the end of the firebase reference it says I can only use one orderBy call.
I tried the filter lesson in lessons | orderByPriority but it says that filter does not exist?
Currently, you can't mix .orderByPriority() and .orderByChild() as they are different order by functions.
However, you can still solve your problem by only using .orderByPriority(), if you re-structure your data.
Structuring your data a specific way allows you query your data, as if you could use two orderBy functions.
In your case you have a location of "Lessons" that all have a push-id key of their "lesson id": /lessons/$lessonID.
You could change your structure to key off of the courseId and then the lesson_id: /lessions/$courseID/$lessonID.
The data would look like this:
{
"lessons": {
"1": {
"-K4NQTsjo3iswX4PhKUw": {
title: "My Course"
},
"-K4NAWsjo5iswX4Jk4fa": {
title: "Another Course"
}
}
"2": {
"-K4NQTsjo3iswX4PhKUw": {
title: "My Course"
},
"-K4NQPjMZVCoFRYc_1v5": {
title: "Intro to Data Structure"
}
}
}
}
Now since the key uses the courseID, we can order by by both courseID and priority:
var courseID = $state.params.courseID;
var ref = new Firebase('<my-firebase-app>.firebaseio.com/lessons');
var query = ref.child(courseID).orderByPriority();
var syncArray = $firebaseArray(query);
You can store this in a factory in your AngularJS code.
angular.module('app', ['firebase')
.constant('FirebaseUrl', '<my-firebase-app>')
.service('RootRef', ['FirebaseUrl', Firebase])
.factory('Lessons', Lessons)
.controller('MainCtrl', MainCtrl);
function Lessons(RootRef) {
return function Lessons(courseID) {
var ref = RootRef.child('Lessons');
var query = ref.child(courseID).orderByPriority();
return $firebaseArray(query);
}
}
function MainCtrl($scope, $state, Lessons) {
var courseID = $state.params.courseID;
$scope.lessons = Lessons(courseID);
}
If Priority is an attribute of lesson, you can use the angular filter orderBy like this:
<ul>
<li class="lesson"
ng-if="lesson.moduleID == module.$id"
ng-repeat="lesson in lessons | orderBy:'Priority'"
lessonid="{{lesson.$id}}">
<lessonedit></lessonedit>
</li>
</ul>
Using Firebase's new Query API, you can do this with:
var ref = new Firebase('https://your.firebaseio.com/');
ref
.orderBy('genre')
.startAt('comedy').endAt('comedy')
.orderBy('lead')
.startAt('Jack Nicholson').endAt('Jack Nicholson')
.on('value', function(snapshot) {
console.log(snapshot.val());
});
Note that the new Query API is still in beta, so things may change before it's officially released.
See this jsbin for a similar query on Firebase's sample data set.
You can check the original Post of Frank van Puffelen here

ng-repeat function called multiple times

I have written ng-repeat like below
<div class="anchor" ng-repeat="race in races" ng-class="showdata (race)"></div>
$scope.showdata = function(data)
{
var raceId = data.raceId;
if(raceId == 66666666)
{
console.log(data);
console.log('----- next ---')
}
and data is something like this
[
{
"raceId": "434346"
},
{
"raceId": "123456"
},
{
"raceId": "222222"
} ,
{
"raceId": "5555555"
}
,
{
"raceId": "32423443"
}
,
{
"raceId": "66666666"
}
]
Ideally data should get printed only once as raceId 66666666 is only once. But i see object printed thrice. Is there any problem with it. Ideally it means it got called thrice with same data?
On a side note from the comment by #tymeJV, which is clearly correct.
If you are looking to set a particular class based on one of the ng-repeat items, you can also use basic logic inside the ng-class itself.
For example:
<div class="anchor" ng-repeat="race in races" ng-class="{'some-class': race.raceId === '66666666'"></div>
This will only apply the some-class to the race item you have listed in your code.

How to get a single item from a GoInstant collection?

How do you get a single item from a GoInstant GoAngular collection? I am trying to create a typical show or edit screen for a single task, but I cannot get any of the task's data to appear.
Here is my AngularJS controller:
.controller('TaskCtrl', function($scope, $stateParams, $goKey) {
$scope.tasks = $goKey('tasks').$sync();
$scope.tasks.$on('ready', function() {
$scope.task = $scope.tasks.$key($stateParams.taskId);
//$scope.task = $scope.tasks.$key('id-146b1c09a84-000-0'); //I tried this too
});
});
And here is the corresponding AngularJS template:
<div class="card">
<ul class="table-view">
<li class="table-view-cell"><h4>{{ task.name }}</h4></li>
</ul>
</div>
Nothing is rendered with {{ task.name }} or by referencing any of the task's properties. Any help will be greatly appreciated.
You might handle these tasks: (a) retrieving a single item from a collection, and (b) responding to a users direction to change application state differently.
Keep in mind, that a GoAngular model (returned by $sync()) is an object, which in the case of a collection of todos might look something like this:
{
"id-146ce1c6c9e-000-0": { "description": "Destroy the Death Start" },
"id-146ce1c6c9e-000-0": { "description": "Defeat the Emperor" }
}
It will of course, have a number of methods too, those can be easily stripped using the $omit method.
If we wanted to retrieve a single item from a collection that had already been synced, we might do it like this (plunkr):
$scope.todos.$sync();
$scope.todos.$on('ready', function() {
var firstKey = (function (obj) {
for (var firstKey in obj) return firstKey;
})($scope.todos.$omit());
$scope.firstTodo = $scope.todos[firstKey].description;
});
In this example, we synchronize the collection, and once it's ready retrieve the key for the first item in the collection, and assign a reference to that item to $scope.firstTodo.
If we are responding to a users input, we'll need the ID to be passed from the view based on a user's interaction, back to the controller. First we'll update our view:
<li ng-repeat="(id, todo) in todos">
{{ todo.description }}
</li>
Now we know which todo the user want's us to modify, we describe that behavior in our controller:
$scope.todos.$sync();
$scope.whichTask = function(todoId) {
console.log('this one:', $scope.todos[todoId]);
// Remove for fun
$scope.todos.$key(todoId).$remove();
}
Here's a working example: plunkr. Hope this helps :)

implement embedding comments like twitter in angularjs

i know in Angular world it is better to bind data than manipulate dom elements. but i can't figure out a way to implement the 'in timeline, click a tweet, load replies, click another tweet load another replies' effects.
here is some code run into my thoughts:
<div class="tweet" ng-repeat="tweet in tweets">
<div class="tweet-content">{{tweet}}</div>
<a class="button" ng-click="loadreplay()">load reply</a>
<div class="reply-container">{{reply}}</div>
</div>
if i write controller like this
app.controller('Test', function($scope){
$scope.tweets = ["foo", "bar"];
$scope.loadreplay = function(){
$scope.reply = "reply";
}
});
then all {{reply}} fields will be filled with 'reply', so in this condition, is manipulate the dom elements the only resolution? or some more "angular" way?
Use a appropriate schema for your data/model. Considering that you would store not only the text but at least something like a ID you would use an object anyway. So think about something like this:
$scope.tweets = [
{ id:1, txt: 'foo' },
{ id:2, txt: 'bar' }
]
Then you could store the individual replies in that object as well:
$scope.loadreply = function(tweet) {
tweet.reply = 'Reply';
}
Note: In this function you could then also use the ID to e.g. fetch the tweets from the server like this:
$scope.loadreply = function(tweet) {
tweet.reply = LoadReplies(tweet.id);
}
You would then use the tweet specific reply attribute for display:
<div ng:repeat="tweet in tweets">
<div>{{tweet.txt}}</div>
<a ng:click="loadreply(tweet)">load reply</a>
<div>{{tweet.reply}}</div>
</div>
See this fiddle for a working demo: http://jsfiddle.net/XnBrp/

Resources