How do I count object keys to determine an expression? - angularjs

I'm trying to hide the parent element of a table (with rows created using ng-repeat) if the table is empty. I've found the follow example:
https://stackoverflow.com/a/14616397/930998
But that only applies to arrays. I have an object with nested objects like this:
{
"-J9dKgyHpCTNNImuydo6" : {
"url" : "http://www.example.se",
"title" : "Example 1",
"uuid" : "-J9dKgyHpCTNNImuydo6",
"sla" : false
},
"-J9dKQ2kxHzFc5-bJIKN" : {
"url" : "http://www.example.com",
"title" : "Example 2",
"uuid" : "-J9dKQ2kxHzFc5-bJIKN",
"sla" : true
}
}
My repeater looks like this:
<tr ng-repeat="site in sites | archived:false">
<td class="title">{{site.title}}</td>
<td class="url">{{site.url}}</td>
<td class="sla">{{site.sla | BooleanToText}}</td>
</tr>
As you can see I have a filter on the repeater as well. What I'm trying to do is hide the whole table if "site in sites | archived:false" is empty. I've tried the following:
ng-show="(sites | archived:false).length"
and
ng-show="(Object.keys(sites) | archived:false).length"
But I can't get it right. I think the length always returns 0 no matter what I do.
Here's a fiddle to make it more visible: http://jsfiddle.net/insats/djD4m/3/
I basically want to hide the wrapping div if the table is empty.

Maybe there is an easier solution but for sure this will work:
Add a method that indicates if the filtered collection is empty:
$scope.isNonArchivedSiteCollectionEmpty = function () {
... // you can use filter here or some simpler logic to determine if there is
... // at least one element in the collection
}
Then in your view just use:
ng-hide="isNonArchivedSiteCollectionEmpty()"
UPDATE Probably the most reasonable way is just to keep and refresh filter collection from the $scope and resign using filter in the markup altogether. Then you could very easily check if it is empty or not.

Related

Rendering information of each child in a list of a denormalized data structure

Let say there is some data of students that are in classes (groups) of different schools:
{
"students": {
"alexa" : {
"firstName" : "ReJina",
"grade" : 2,
"lastName" : "idk",
...
},
"bobby" : { ... },
"chris" : { ... },
...
},
"schools": {
"springfield" : {
"name" : "Springfield Elementary",
"location" : "123 Main St",
...
},
"quahog" : { ... },
"zion" : { ... },
...
},
"group": {
"one" : {
"members" : {
"alexa" : true,
"bobby" : true,
"chris" : true
},
"school" : {
"springfield" : true
},
"name" : "Math Class"
},
"two" : {
"members" : {
"bart" : true,
"lisa" : true,
"meg" : true,
...
},
"school" : {
"quahog" : true
},
"name" : "Art Class"
},
"three" : { ... },
"four" : { ... },
...
}
}
To my knowledge this is the proper way of structuring data in a flat way. Please let me know if this is incorrect. I keep the associated lists on each item, so each class needs a roster (members) and if I also had the situation where a student was in more than one class (group) or school, those would need lists of classes and schools they attend.
Now in the HTML we need to have a view that shows all of the schools classes (groups) and the students in each class and the students in the same view with a nested ng-repeat:
<div id="data-table-div" class="col-md-6">
// Get all the groups, and show them one by one
<div ng-repeat="group in groups">
// This works - How do I get the school it is associated to?
// ??
{{group.name}} - {{group's school's name}}
//Start a table
<table>
<thead> <tr> <th>Student Name</th> <th>Grade</th> ... </thead>
<tbody>
// Get the students in each group, and show them one by one
// ??
<tr ng-repeat="student in groups.members">
<td>{{student.name}}</td> <td>{{student.grade}}</td> ...
</tr>
</tbody>
</table>
</div>
</div>
And in the controller :
angular.module('sofDataViewerApp')
.controller('dataTableController', function ($scope, $firebaseObject, $firebaseArray) {
// This gets all of our groups. It also allows `group.memebers` to render the appropriate number of `<tr>`s as it knows it has N students.
var groupsRef = new Firebase("https://sof-data.firebaseio.com/studyGroups");
$scope.groups = $firebaseArray(groupsRef);
// This will give all the students. How do I get just the template's `group.members` to be each appropriate student?
var studentsRef = new Firebase("https://sof-data.firebaseio.com/students");
$scope.allStudents = $firebaseArray(studentsRef);
});
Question:
How do I get the HTML template to properly iterate over the groups' members and I have access to all of the data of each student (such as their firstName and grade)? (I am assuming it may involve better code in the controller and possibly in the HTML)
PLNKR: http://plnkr.co/edit/uAn1Ky9v5NHHDFPs2c6S?p=preview
(If you know how to inject Firebase and angularFire in the plnkr, then it should work, but I still can't figure that out despite including the CDN of them...)
The closest explanation I can understand is on this page by Firebase, they suggest using a LINK_ID, but it is not described as what they mean, esp since they talk about not searching by id. In my case, a link would be synonymous with group and a comment would be a student.
var commentsRef = new Firebase("https://awesome.firebaseio-demo.com/comments");
var linkRef = new Firebase("https://awesome.firebaseio-demo.com/links");
var linkCommentsRef = linkRef.child(LINK_ID).child("comments");
linkCommentsRef.on("child_added", function(snap) {
commentsRef.child(snap.key()).once("value", function() {
// Render the comment on the link page.
});
});
It looks like you're trying to join multiple "tables". The best way to do that with AngularFire is by extending $firebaseArray (link to docs). With this you can load the additional data from the other locations as each child get added (or is modified).
Also see this answer from Kato that show extending an array (although for a different use-case).
Finally you can consider duplicating the data that is necessary for each screen into the list for that screen. While Firebase is pretty fast to retrieve joined data (it keeps an open connection and can pipeline multiple requests), it will always be faster if you have to do fewer requests to get the data.
An example of this would be if you want to display a list of the members in a group:
"group": {
"one" : {
"members" : {
"alexa" : "ReJina",
"bobby" : "Robert",
"chris" : "Christian"
...
So instead of just storing true, we actually store the first name of each member here. This allows us to get all the data to show the group members with a single read operation:
ref.child('group/one/members').on('value'...
Then when the user clicks on one of the group members, we'd go to that group member's page and load the data from /students/$studentid and possible have the groups that they're part of (and the names of those groups) under there too..

Angular: a better way to filter when you're only expecting one result

I'm using the following angular code to get one item out of a list.
<span ng-repeat="metro in metros | filter:{id:event.metro_id}">
{{metro.timezone}}
</span>
So there's a list of metros and I'm filtering the list for metros where the id matches event.metro_id. The thing is, for my scenario I know there's always going to be 1, and only 1, match for this filter and it feels silly to use a ng-repeat just to get that one element. Is there a more elegant way to do this. My goal is to do it all in the HTML template and avoid placing more code in the angular controller.
If you could slightly modify your metros structure (only in favour of what you want to), to be a collection with id's as properties, then you could do like this in HTML:
<span ng-show="event.metro_id">
{{metros[event.metro_id].timezone}}
</span>
I meant to have metros like this:
$scope.metros = {
"25" : {
"timezone" : 25,
"foo" : "bar"
},
"35" : {
"timezone" : 35,
"foo" : "bar"
}
}

Angular - Filter based on nested array boolean

I'm trying to find a good way to filter on click, based on a boolean value that's within a nested array.
My current scope looks like the following
$scope.persons = [
{
firstName : 'Drew',
lastName : 'Minns',
views : [
{
name : 'View 1',
support : true
},
{
name : 'View 2',
support : false
}
],
},
{
firstName : 'Peter',
lastName : 'Parker',
views : [
{
name : 'View 1',
support : false
},
{
name : 'View 2',
support : false
}
],
}
];
I'm looking to add a filter that sorts based on the boolean value of each view object. The problem that I'm running into is that I can't access that value without iterating over every array. Doing that is making it tough to access each individual value without referencing the array number.
I want to hide the parent object based on whether or not the specific "view" object has a true or false in the support field.
Again, I'm looking to do this on click, so the idea is that you click a button for 'View 1' and only those parent objects with true value for support shows up. There will be multiple buttons for each "view" so I'm looking to provide the action of drilling down based on support for views.
Here's a plunkr http://plnkr.co/edit/ipi8vKEbxps2H89HTg00?p=preview
You can use Angular JS's "Filter" function to do this. Plunkr example, with the relevant change below
http://plnkr.co/edit/LHTpRqHbTxEAslY0rd5J?p=preview
<ul ng-repeat="view in person.views | filter:{ support: true }">
Edit: For what you want, I've slapped together a quick custom filter: http://plnkr.co/edit/LHTpRqHbTxEAslY0rd5J?p=preview

Why this Angular filter is not working

I've got a filter so I can search for a list of products. The catch is, the list o products is nested (I've got an object which is called category, which contains an object with a series of objects called subcategories, which in turn contains an object called products that contains a series of products. These are the ones I want to filter on). My code is as follows (I'm using Jade btw):
accordion-group(ng-repeat="category in categories", is-open="category.active")
...
tab(ng-repeat="subcat in category.subcategories", active="subcat.active")
...
input(type="text", ng-model="searchText")
tr(ng-repeat="prod in subcat.products|filter:searchText")
...
I've used Angular's filter similarly before and it worked fine (not nested within ng-repeats though) and I can't see what I'm missing.
Any ideas?
EDIT: Adding subcat.products structure
"46ad-97d9-bd3cfe" : {
"name" : "Meat",
"id" : "46ad-97d9-bd3cfe",
"editing" : false,
"active" : true,
"products" : {
"45ed-a686-e45e52" : {
"name" : "Sirloin Steak",
"price" : 10,
"sku" : "MT001",
"id" : "45ed-a686-e45e52",
"editing" : false
},
...
}
},
Many thanks

In AngularJS, is it possible to filter inside of a specific element of an array using only a view?

I've got some data like this:
people = [
{
names: [
{first: "Joe", last: "Smith"},
{first: "Joseph", last: "Smith"},
...
]
},
...
]
In other words, an array of objects with an array of names. For example, a person could be called "Joe Smith" or "Joseph Smith". How can I use a filter to only search the first element of names? IE: If I typed in "Jo" or "Smith" it would find the first person. But, if I typed in "seph" it wouldn't.
I've been looking at the examples on this page, but there isn't really an example of filtering inside arrays. Here's what I've tried but it gives me an error:
<input ng-model="search.names[0].$">
TypeError: Cannot set property '$' of undefined
Working Code
Input HTML
<input ng-model="searchTerm">
Results
<tr ng-repeat="p in people | filter:searchFunc">...</tr>
Controller
$scope.searchFunc = function(person) {
var firstPersonsName = [person.names[0]]; // Wrapping in array since the 'filter' $filter expects an array
var matches = $filter('filter')(firstPersonsName, $scope.searchTerm); // Running firstPersonsName through filter searching for $scope.searchTerm
return matches.length > 0;
}
Plunker Demo
Answer to the question in your title
I played around with filter and it doesn't seem like you can go beyond one level deep when specifying a pattern object for it e.g. ng-model="search.names" works but ng-model="search.names.otherVal" doesn't.
Also, even if filter supported going beyond one level deep, you still wouldn't be able to do ng-model="search.names[0]". This is because filter expects the input to be an array, but the elements of your names array are all objects e.g. people[0].names[0] == {first: "Joe", last: "Smith"} so filtering will never work.
The only way to do what you are asking purely through the view and no extra code in your controller is to just create your own custom filter that handles your case.
Would this do the trick?
Say that your input is
<input ng-model="search.name" type="text">
Then, you can display results like this:
<div ng-repeat="person in people[0].names | filter: alias">...</div>
Controller:
$scope.alias = function (object) {
if (object.first.match(new RegExp($scope.search.name))) {
return true;
}
return false;
};

Resources