AngularFire: How to filter a synchronized array? - angularjs

Consider a set of items and users. Each user can have one or more items. The set of items can be quite big, but every user will normally have small amount of items.
app:
items:
item1:
name: 'table'
color: 'white'
createdAt: '2014-08-09T12:54:58.803Z'
item2:
name: 'macbook air'
color: 'silver'
createdAt: '2014-06-09T12:54:58.803Z'
item3:
name: 'firebase t-shirt'
color: 'yellow'
createdAt: '2014-07-09T12:54:58.803Z'
users:
user1:
items:
item1: true
item3: true
user2:
items:
item2: true
Given a user id, e.g. user1, I would like to display a list of user's items, sorted by createdAt:
yellow firebase t-shirt
white table
Every update on the Firebase server should be reflected in the app view.
I guess the view should look like this:
<div ng-repeat="item in items | orderBy:'createdAt'">
{{ item.color }} {{ item.name }}
</div>
But, I can't figure out an easy way to set up $scope.items.
This is what I currently do:
var userItemsRef = new Firebase(FIREBASE_ROOT + '/users/user1/items');
$scope.userItems = $firebase(userItemsRef).$asArray();
$scope.userItems.$loaded(function() {
$scope.userItems.$watch(setItems);
setItems();
});
function setItems() {
var promises = $scope.userItems.map(function(userItem) {
var itemRef = new Firebase(FIREBASE_ROOT + '/items/' + userItem.$id);
return $firebase(itemRef).$asObject().$loaded();
});
$q.all(promises).then(function(items) {
$scope.items = items;
});
}
Is this the best way to utilize AngularFire?

It would probably be simplest to store the items by user. So a structure like this:
/app/items/$user_id/$item_id/...
This would allow for the items belonging to a particular user to be retrieved like so:
var userId = 'user1';
var ref = new Firebase(FIREBASE_ROOT + '/items/' + userId);
$scope.items = $firebase(ref).$asArray();
If this isn't possible, because items are shared between users, it's probably simplest to use a join lib.
var userId = 'user1';
var userIndexRef = new Firebase(FIREBASE_ROOT + '/users/' + userId + '/items');
var itemsRef = new Firebase(FIREBASE_ROOT + '/items/');
var ref = Firebase.util.intersection(userIndexRef, itemsRef);
$scope.items = $firebase(ref).$asArray();
But generally, it's safest and simplest to take the first route.

Related

How to save nested objects in my Firebase db

I have created a view for adding categories and a view for products. I want to add products to categories. This is working but it would probably be more efficient to have it structed like below instead of what I've got in the screenshot.
products
- KXbMe8zo14TyOYDJh4q
cat: {
id: "-KXXJY-i7Avnqw8IMqR", name: "Starters"
}
Would this structure be better and how should I implement it? I'm expecting the whole cat object to be passed in as I'm referening product.cat as the model.
If I set the value in the form element to be {{cat}} I get this in the DB:
"{\"name\":\"Kebabs\",\"$id\":\"-KXXJl0Ice17zDIYF_Xt\",\"$priority\":null}"
Does this look its correct?
<select ng-model="product.cat">
<option ng-repeat="cat in cats" value="{{cat.$id}}">{{cat.name}}</option>
</select>
I'm retrieving the categories like so:
var cats = $firebaseArray(firebase.database().ref('category'));
cats.$loaded().then(function(){
$scope.cats = cats;
});
I've ended up with the following:
var cats = $firebaseArray(firebase.database().ref('category'));
cats.$loaded().then(function(){
$scope.cats = cats;
});
$scope.addProduct = function() {
console.log($scope.product);
var category = {};
for(var i=0; i<$scope.cats.length; i++){
if($scope.cats[i].$id == $scope.product.catid){
category.id = $scope.cats[i].$id;
category.name = $scope.cats[i].name;
}
}
//var record = firebase.database().ref().child('products/category').push($scope.product.cat);
//var record = firebase.database().ref().child('products').push($scope.product);
var record = firebase.database().ref().child('products').push({
'category' : {id: category.id, name: category.name},
'name' : $scope.product.name,
'price' : $scope.product.price
});
if(record.key){
$scope.productSuccess = true;
} else {
$scope.productSuccess = false;
}
};
This gives me the following in my DB:

ngRepeat with custom index coming up blank

I'm using a large amount of arrays in a large form in my application. In order to make splicing out specific data from my datasets based on the user's selections, I've structured my arrays like this example:
var userList = [];
userList[user1.id] = user1;
userList[user2.id] = user2;
This lets me splice out specific elements without looping through the entire collection by using:
userList.splice(user1.id, 1);
However, when I try to make a list of Users in my HTML using an ng-repeat statement, it comes up blank. My HTML is:
<div data-ng-repeat="user in userList">{{user.name}}</div>
I suspect that ngRepeat uses 0,1,2.. by default and doesn't know how to handle my custom indexes. I've checked several sources but I can't really make sense of things. It did work when I added my users by simply pushing them into the array instead of assigning them to a specific index, so I know the rest of my code works fine.
Help? D:
EDIT:
The addition of a trackBy "track by user.id" didn't fix it.
Plunkr! http://plnkr.co/edit/8hANBvXAIplHsq0Ph6GX?p=preview
Your code isn't working because Array's indexes are zero-based meaning, they go from 0, 1, 2, ... n and you're trying to put alphanumeric indexes if you check the below code snippet the length of the array is zero.
var user1 = {
id: 'A1B2',
name: 'Pete'
};
var user2 = {
id: 'A2B3',
name: 'Jeff'
};
var userList = [];
userList[user1.id] = user1;
userList[user2.id] = user2;
console.log(userList);
console.log('length: ' + userList.length);
console.log(userList['A1B2']);
console.log(userList.A1B2); // behaving as JavaScript Object not array as property set using userList[user2.id] = user2;
So you need to set the data structure properly, you can set it as follows specifying the index of the array or by using the push function on the array to add a new item to the array.
var user1 = {
id: 'A1B2',
name: 'Pete'
};
var user2 = {
id: 'A2B3',
name: 'Jeff'
};
$scope.userList = [];
$scope.userList[0] = user1; // $scope.userList.push(user1);
$scope.userList[1] = user2; // $scope.userList.push(user2);
I suggest you change the collection name from userList to users it looks clean, you don't need to suffix a collection with the List keyword I think it looks untidy, just make the name plural.
angular
.module('demo', [])
.controller('DefaultController', DefaultController);
function DefaultController() {
var vm = this;
var pete = {
id: 'A1B2',
name: 'Pete'
};
var jeff = {
id: 'A2B3',
name: 'Jeff'
};
vm.users = [];
vm.users[0] = pete;
vm.users[1] = jeff;
}
<script src="https://ajax.googleapis.com/ajax/libs/angularjs/1.2.23/angular.min.js"></script>
<div ng-app="demo">
<div ng-controller="DefaultController as ctrl">
<div ng-repeat="user in ctrl.users">
<span>{{user.id}}, {{user.name}}</span>
</div>
</div>
</div>
Do following
Controller:
$scope.userList = [];
$scope.userList.push(user1);
$scope.userList.push(user2);
View:
<tr ng-repeat="user in userList track by $index">
<td>{{user.id}}</td>
<td>{{user.name}}</td></tr>
change the id of each individual user to numeric from alpha-numeric as pointed by Adbul, array's indexes are number based
for eg:-
var user1 = {
id: 0, //Any numeric Value
name: 'Pete'
}
and then try $scope.userList[user1.id] = user1;
<div data-ng-repeat="user in userList">{{user.name}}</div>

How do I push review objects into an existing model's array using Angular and a form?

First off, I am very new to Angular, and working on my first real MEAN app.
I am building an app that allows users to leave reviews on surf spots. I already have a model for surf spots, and each surf spot has an array that is supposed to hold reviews.
I'm not sure how I can go about using a form to add a review into the array for that spot. Do I need to create a Schema for the reviews? Do I need to create another controller and service for reviews in order to add them to my spots?
I know this is a pretty broad question, but I could really use some direction if anyone can offer it.
Spot Model:
var mongoose = require("mongoose"),
Schema = mongoose.Schema;
var SpotSchema = new Schema({
name : String,
location : String,
reviews : Array,
latitude : Number,
longitude : Number,
tide : String,
rating : Number,
region : String
});
module.exports = mongoose.model("Spot", SpotSchema);
Spot Controller:
angular.module("spotCtrl", ["spotService"])
.controller("spotController", function(Spot) {
var vm = this;
vm.processing = true;
Spot.all()
.success(function(data) {
vm.processing = false;
vm.spots = data;
});
})
.controller("singleSpotController", function($routeParams, Spot) {
var vm = this;
vm.processing = true;
Spot.get($routeParams.spot_id)
.success(function(data) {
vm.processing = false;
vm.spotData = data;
})
})
Spot Service:
angular.module("spotService", [])
.factory("Spot", function($http) {
var spotFactory = {};
spotFactory.get = function(id) {
return $http.get("/api/spots/" + id);
};
spotFactory.all = function() {
return $http.get("/api/spots/");
};
return spotFactory;
});
Finally, the view that displays the data for a single Spot. This is where the form needs to go, but I'm not sure how I can use a form to create a review object, and then push it into the array for that spot.
Spot View:
<div ng-model="singleSpot.spotData">
<div class="text-center">
<div class="single-spot">
{{ singleSpot.spotData.name }} <br>
{{ singleSpot.spotData.location }} <br>
Rating: {{ singleSpot.spotData.rating }} <br>
Current Tide: {{ singleSpot.spotData.tide }}
</div>
</div>
<form>
<!-- What do I do here? -->
</form>
</div>
Thanks!
As far as I understand you want a spot to have multiple reviews.
var SpotSchema = new Schema({
name : String,
location : String,
reviews : [{type: Schema.Types.ObjectId, ref: 'Review'}],
latitude : Number,
longitude : Number,
tide : String,
rating : Number,
region : String
});
Then create a new schema for your reviews
var ReviewSchema = new mongoose.Schema({
review: String,
_Spot: {type: Schema.Types.ObjectId, ref: 'Spot'},
created_at: Date
});
Inside your Html
<div ng-controller="reviewController"
<form>
<textarea name="review" ng-model="new_review"></textarea>
<input type="submit" value="Submit" ng-click="addReview(singlespot.SpotData)">
</form>
</div>
Then create your reviewController and factory and send the data to the Db as you are already doing. Note you have passed the singlespot.SpotData along with ur addReview so you can repack it and use it to findOne spot and then push the review inside the spot.reviews
your backend will look something like this
Spot.findOne({_id: req.body.spot_id}, function (err, spot){
var review = new Review(req.body);
review._Spot = Spot._id;
spot.reviews.push(review);
review.save(function(err){
spot.save(function(err){
if(err) {
console.log("Error");
}
else{
console.log("Succesfully added Review");
}
});
});
});
basically you have to find your spot then push the review into the spot.reviews and save the review as well.
Hope it makes sense.

How to get and compare values in table from another table in angularjs?

I am new at angularjs. So, it might be fool question.Anyway, please let me explain my problem. I have a table which is listed by ng-repeat and I'd like to change a column datas with another datas in another table column.
<tr data-ng-repeat=" list in listTypes">
<td>{{list.Comments}}</td>
<td>{{list.Modul}}</td>
<td>{{list.UserId}}</td>
<td data-ng-repeat="user in userNames">{{user.UserName}}</td>
I want to get UserName instead of UserId, but the problem that UserName is recorded in another table. Here is my angular for getting listTypes :
$scope.GetList = function () {
var onSuccess = function (response, status) {
//1
$scope.listTypes = response.Data;
var str = response.Data;
$scope.listTypes = eval('(' + str + ')');
for (var key in $scope.listTypes) {
$scope.listTypes[key].selected = "";
}
$scope.GetUserNames();
};
var data = null;
var request = $rest.GetList(data);
NGTools.CallNgServiceWithRequest(request, onSuccess, "GetList");
};
And trying to get usernames with this code:
$scope.userdatas= [];
$scope.userNames = [];
$scope.GetUserNames = function () {
var onSuccess = function (response, status) {
//1
$scope.userNames = response.Data;
};
$scope.userdatas= $scope.listTypes.UserId;
var data = { userdatas: JSON.stringify( $scope.userdatas) };
var request = $rest.GetUserNames(data);
NGTools.CallNgServiceWithRequest(request, onSuccess, "GetUserNames");
};
but it doesn't work. I couldn't figure out what's wrong with this code block. Please let me know if any tip is available. Thank you!
Assuming that you have to collections in your scope - one of which holds the id of the user, and the other holding the name, like so:
$scope.users = [
{ id: 1, name: 'John Doe' },
{ id: 2, name: 'Jane Doe' },
{ id: 3, name: 'Janice Doe' } ];
$scope.userInfo = [
{ userId: 1, gender: 'male' },
{ userId: 2, gender: 'female' },
{ userId: 3, gender: 'female' }];
Then what you could do is ng-repeat over the one with the userInfo and in your binding expression - use the id to get the name from the other collection:
<li ng-repeat="item in userInfo">
{{ item.gender }} {{ getNameFor(item.userId) }}</li>
Where the getNameFor is defined as:
$scope.getNameFor = function(id) {
var user = $scope.users.filter(function(item) { return item.id === id })[0];
console.log(user);
return user.name;
Which I checked in a fiddle here: http://jsfiddle.net/01kmoxw9/

AngularJS matching data between json arrays and setting a selected option

$scope.opts =
{
unit: [
{ id: 1, val: "px", name: "px"},
{ id: 2, val: "%", name: "%"}
]
}
The above is my options list array and now I set my default option.
$scope.user.unit = $scope.opts.unit[0];
The above creates the following in my html
<select class="unit ng-pristine ng-valid" data-ng-options="a.name for a in opts.unit" data-ng-model="user.unit">
<option value="0" selected="selected">px</option>
<option value="1">%</option>
</select>
When I use the below I am pulling the data that was stored in a db from the options selected in the above example.
$http.get('/assets/inc/file.php?id='+thisPage).success(function(response) {
var userData = response.userData;
var locationData = response.locationData;
$scope.user = userData;
$scope.locations = locationData;
console.log($scope.user.unit);
});
This console.logs me the following Object { id=1, val="px", name="px"}
I may be wrong but the <select> box is binded to $scope.opts
How would I be able to link the retrieved data from $scope.user.unit to $scope.opts.unit so that when the data is retrieved it will then mark the correct option as :selected?
I'm not 100% sure but you can try this (or create JSFiddle):
JS:
$http.get('/assets/inc/file.php?id='+thisPage).success(function(response) {
var userData = response.userData;
var locationData = response.locationData;
$scope.user = userData;
$scope.locations = locationData;
$scope.selected = {};
angular.forEach($scope.opts.unit, function (value)
{
if (value.val == $scope.user.unit.val) {
$scope.selected = value
}
});
console.log($scope.user.unit);
});
and in View:
<select class="unit ng-pristine ng-valid" data-ng-options="a.name for a in opts.unit" data-ng-model="user.unit">
<option value="{{selected.val}}">{{selected.name}}</option>
</select>
Your ng-model for the select element is an object, and not a primitive type, which is fine, but then you reassign $scope.user to a brand new object (returned from $http.get), so user.unit is a new object too, so it's not identical to any of your ng-options. I can think of two ways which should fix the problem:
bind the select to the 'id' property of the unit object:
<select ng-options="a.id as a.name for a in opts.unit" ng-model="user.unit.id">
or leave the select bound to user.unit, but use the track by feature of ng-options:
<select ng-options="a.name for a in opts.unit track by a.id" ng-model="user.unit">
One of the things in Angular is that you rarely need to do is explicitly create <option> elements manually as the framework will generate this for you. Therefore, the following will work: (Working jsfiddle at http://jsfiddle.net/LMHLq/12/)
HTML:
<select data-ng-model='user.unit' data-ng-options="o.id as o.name for o in opts.unit"/>
JavaScript:
$scope.opts ={
unit: [
{ id: 1, val: "px", name: "px"},
{ id: 2, val: "%", name: "%"},
{ id: 3, val: "pt", name: "pt"}
]
}
$http.get('/assets/inc/file.php?id='+thisPage).success(function(response) {
var userData = response.userData;
var locationData = response.locationData;
$scope.user = userData;
$scope.locations = locationData;
console.log($scope.user.unit);
});
$scope.opts ={
unit: [
{ id: 1, val: "px", name: "px"},
{ id: 2, val: "%", name: "%"},
{ id: 3, val: "pt", name: "pt"}
]
}
I noticed that the $scope.opts builds my select element and populates it but when the data is retrieved via db it needs to go into $scope.user.unit but this is binded to $scope.opts so what I have done is sought out the ID for the item that was retrieved and then added -1 to it so it will select from the array of $scope.opts.unit
var testUnit = $scope.user.unit.id-1; //gets the ID of the unit thats been retrieved
$scope.user.unit = $scope.opts.unit[testUnit]; //sets the selected option in the dom

Resources