How to get a data of object from Firebase 'key':'true' patern - angularjs

I have this firebase data:
{
"userData": {
"user01": {
"name": "User One"
"provider": "provider0001"
},
"user02": {
"name": "User Two"
"provider": "provider0001"
},
"user03": {
"name": "User Three"
"provider": "provider0002"
}
},
"provider": {
"provider0001": {
"users": {
"user01": true,
"user02": true
}
},
"provider0002": {
"users": {
"user03": true
}
}
}
}
controller
vm.provider = $firebaseObject(ref.child('provider').child('provider0001'));
simple html
<ul>
<li ng-repeat="user in $ctrl.provider.users">
{{user}}
</li>
</ul>
Why I cannot list {{user}} as object? The above will display list of true's, but how can I access the user object it self?

You need to map the keys (your userId's) in the users-object, to the values in your userData-object. You can get the keys in an ng-repeat-directive like this:
ng-repeat="(key, value) in $ctrl.provider.users"
Then in your controller you need to add the userData object to your controller scope:
vm.userData = $firebaseObject(ref.child('userdata'));
So you can implement your ng-repeat like this:
<ul>
<li ng-repeat="(key, value) in $ctrl.provider.users"
ng-init="user = $ctrl.userData[key]">
{{user}}
</li>
</ul>
With that said, you probably ought to do this mapping when you retrieve the data in the controller initially. Something like this:
function loadData(){
var providerUsers = $firebaseObject(ref.child('provider').child('provider0001').child('users'));
var userData = $firebaseObject(ref.child('userData'));
vm.users = [];
angular.forEach(providerUsers, function(value, key) {
vm.users.push(userData[key]);
});
}
Then you can just iterate using ng-repeat over this new vm.users-array.
Update
Here's a solution where you only retrieve the actual mapped users. It makes a query for each user, since I couldn't find a decent way to join two collections by key using just a single Firebase query.
function loadData(){
var userRef = ref.child('provider').child('provider0001').child('users');
var userDataRef = ref.child('userData');
vm.users = [];
userRef.once('value', function(snapshot){
snapshot.forEach(function(userId){
userDataRef.child(userId.key()).once("value", function(user){
$scope.$apply(function () {
vm.users.push(user.val());
});
});
})
});
}
My familiarity with Firebase is very limited so there might be better solutions out there

<ul>
<li ng-repeat="(key,user) in $ctrl.provider.users">
{{key}}:{{user}}
</li>
key will give the key of the user and user will give value

Related

Meteor angular template function call

I have been having problems rendering a template function call from a meteor/angular template. I am trying to use the Moment.js package to format my time stamp. I have seen examples of this done with Blaze however, I can't replicate it in Meteor with Angular. My issue is with the formatTimestamp() function call. It isn't rendering anything and there are no errors in my console. Where am I going wrong?
My template
<li ng-repeat="task in $ctrl.tasks" ng-class="{'private' : task.private}">
<details>
<summary>{{task.administration.inventoryNumber}}</summary>
<ul class="bxslider">
<li><h3>Adminstration</h3>
<table>
<td>created at: </td><td>{{formatTimestamp(task.administration.createdAt)}}</td>
My controller
class TodosListCtrl {
constructor($scope) {
$scope.viewModel(this);
this.subscribe('tasks');
this.helpers({
tasks() {
const selector = {};
return Artefacts.find(selector, {
sort: {
createdAt: -1
}
});
},
currentUser() {
return Meteor.user();
},
formatTimestamp: function (timestamp) {
alert("timestamp");
console.log(timestamp);
return moment(timestamp).calendar();
}
})
}
Insert Mongo function
Artefacts.insert({
administration: {
inventoryNumber: invNum,
boxNumber: boxNum,
createdAt: new Date(),
Your custom function goes outside of the constructor, like this
class TodosListCtrl {
constructor($scope) {
$scope.viewModel(this);
this.subscribe('tasks');
this.helpers({
tasks() {
const selector = {};
return Artefacts.find(selector, {
sort: {
createdAt: -1
}
});
},
currentUser() {
return Meteor.user();
}
})
}
formatTimestamp(timestamp) {
alert("timestamp");
console.log(timestamp);
return moment(timestamp).calendar();
}
}
But also, remember in the view to call formatTimeStamp with $ctrl as prefix because it looks like you are using the ControllerAs syntax.
Your code:
<li ng-repeat="task in $ctrl.tasks" ng-class="{'private' : task.private}">
<details>
<summary>{{task.administration.inventoryNumber}}</summary>
<ul class="bxslider">
<li><h3>Adminstration</h3>
<table>
<td>created at: </td><td>{{formatTimestamp(task.administration.createdAt)}}</td>
How it should be:
<li ng-repeat="task in $ctrl.tasks" ng-class="{'private' : task.private}">
<details>
<summary>{{task.administration.inventoryNumber}}</summary>
<ul class="bxslider">
<li><h3>Adminstration</h3>
<table>
<td>created at: </td><td>{{$ctrl.formatTimestamp(task.administration.createdAt)}}</td>
don't need to create a function for date format. It is bad practice to call a function with angular expressions {{}}. You can format your date in Meteor helper.
this.helpers({
tasks() {
const selector = {};
this.value = Artefacts.find(selector, {
sort: {
createdAt: -1
}
// add filter here on createdAt
$filter('filter')(this.value.cretedAt);
});
},

In AngularJS, $http doesn't change my select option tag data

I have an issue in AngularJS on using $http to fetch data from server.
Here is my HTML
<select ng-model="foo" ng-options="item as item.name for item in items"></select>
Here is my AngularJS Script
angular.module("myApp").controller("myCtrl", function($scope, $http) {
var $scope.items = [];
$scope.items = [
{
"id": 1,
"name": "item1"
},
{
"id": 2,
"name": "item2"
},
];
getData();
function getData() {
$http.get("ng/getData")
.then(function(response) {
if (response.status == 200) {
$scope.items = response.items;
/*
$scope.items = [
{
"id": 5,
"name": "item11"
},
{
"id": 4,
"name": "item22"
}
];
*/
}
});
}
});
What expect from this code is when $http fetches data from server, then select dropdown data will change. But it's not changing anything. I have also printed the response items in the console inside the success callback.
Maybe I don't understand $http usage well enough. Perhaps when I console out data after getData(); $scope.items doesn't change at all. And I think that maybe $http always run at last stage.
Can anybody help to explain this issue? If my assumption is correct, what is the solution that I am looking for?
I think you simply have to add a track by clause:
ng-options="item as item.name for item in items track by item.id"
Check the response object. When you are using 'then' callback to get the resolved data then the actual API result is stored in the 'data' property of the response. So change your code to
$scope.items = response.data;
Or you can use the success callback to attach the data directly.
$http.get("ng/getData")
.success(function(response) {
if (response.status == 200) {
$scope.items = response.items;
}
});

AngularJS binding not occurring on page until a user action

I've got my first angular app which displays some data from a list via ng-repeat.
The controller for the view sets a few variables to scope - some directly in the function and another from an API call.
The data from the in function load is showing up in that ng-repeat. The data from the service call doesn't show up (debugging shows the function is being called and data returned and set in scope).
I've got a filter on and if I type anything in it then the data shows up. Or when I click to another view the data flashes onto the page briefly before it loads the new view.
Here is some view code (the items works, venues does not):
<div ng-repeat="item in items">
{{ item.firstName }}
</div>
<div ng-repeat="venue in venues">
{{ venue.details }}
</div>
And here is the controller (data is coming back from the call):
$scope.items = [
{ "firstName": "John", "lastName": "Doe" },
{ "firstName": "Anna", "lastName": "Smith" },
{ "firstName": "Peter", "lastName": "Jones" }
];
var client = new WindowsAzure.MobileServiceClient("url", "key");
var query = client.getTable("venues").read().done(function (results) {
$scope.venues = results;
}, function (err) {
alert("Error: " + err);
});
I'm wondering if maybe the binding is happening before the data is returned from the API?
I added a div and this line into the function and it is printing the results to the page no issues:
document.getElementById("venueslist").innerHTML = JSON.stringify(results);
Thank you for reading.
Looking at your code client.getTable doesn't look like it is using any of angularJs $http or $timeout service. So you will have to wrap the assignment in scope.$apply() so that the $digest cycle is run and the bindings are updated in the view.
var query = client.getTable("venues").read().done(function (results) {
$scope.$apply(function () {
$scope.venues = results;
});
}, function (err) {
alert("Error: " + err);
});
Side note: why are you doing JSON.parse(JSON.stringify(results)), you can directly use results if it is a json object.

Nested/embedded $resources in ngRepeats

My API populates some nested models. Using a shopping example, when you query orders, the items property is populated with the description and quantity instead of returning just the item IDs.
orders: [
{_id: 1,
items: [
{_id: 100,
description: "apple", // from lookup in the Items table
quantity: 4 // from lookup in the Items table
}, ...
],
...
}, ...
]
My view looks like:
<div ng-repeat="order in vm.orders">
<ul ng-repeat="item in order.items">
<li ng-model="vm.orders[$parent.$index].items[$index].description" ng-blur="item.$update()"></li>
<li ng-model="vm.orders[$parent.$index].items[$index].quantity" ng-blur="item.$update()"></li>
</ul>
</div>
The goal is to let the user update the item description and quantity from this view. (The lis are contenteditable directives.) The update call should be made on the Item $resource, not the Order $resource.
Is there a way to get Angular to recognize that the embedded documents are Items, upon which I can call methods like $update?
The workaround that I have is to change the ng-blur to call a controller method equivalent to this:
ng-blur="Item.update({_id: item._id})"
(If I do that, there's also no point in using the $parent.$index and $index syntax -- just order.item and item.description.)
What you want to do is transform the nested resources into actual resources. To do so, add a response transformer to your Order resource. For example
.factory('Order', function($resource, $http, Item) {
var responseTransformer = function(order) {
order.items = order.items.map(function(item) {
return new Item(item);
});
return order;
};
// somewhat lifted from https://docs.angularjs.org/api/ng/service/$http#overriding-the-default-transformations-per-request
var appendResponseTransformer = function(transformer) {
var defaults = $http.defaults.transformResponse;
defaults = angular.isArray(defaults) ? defaults : [defaults];
return defaults.concat(transform);
};
return $resource('/orders', whatever, {
get: {
method: 'GET',
url: '/orders/:id',
params: { id: '#id' },
transformResponse: appendResponseTransform(responseTransformer)
},
query: {
method: 'GET',
isArray: true,
transformResponse: appendResponseTransform(function(orders) {
return orders.map(responseTransformer);
})
}
});
});
An alternative to this would have been to just handle the item update in your controller. For example
ng-blur="updateItem(item)"
with
$scope.updateItem = function(item) {
Item.update(item);
};

adding more than one sources of data to angular

this is what I have in my model
// The contents of individual model .js files will be concatenated into dist/models.js
(function() {
// Protects views where angular is not loaded from errors
if ( typeof angular == 'undefined' ) {
return;
};
var module = angular.module('myModel', ['restangular']);
module.factory('myRestangular', function(Restangular) {
return Restangular.withConfig(function(RestangularConfigurer) {
RestangularConfigurer.setBaseUrl('http://localhost/data');
RestangularConfigurer.setRequestSuffix('.json');
RestangularConfigurer.setRestangularFields({
id: "my_id"
});
});
});
})();
this is fine. but now I have another json that I need to grab data from. How could I change this model to look for that other json as well. I am very very new to angular and still learning how model data binding works!
*This is what I have tired *
my model
var module = angular.module('FloorModel', ['restangular']);
module.factory('FloorRestangular', function(Restangular) {
return Restangular.withConfig(function(RestangularConfigurer) {
RestangularConfigurer.setBaseUrl('http://localhost/data/floor');
RestangularConfigurer.setRequestSuffix('.json');
RestangularConfigurer.setRestangularFields({
id: "floor_id"
});
});
});
**my controller**
myApp.controller('FloorCtrl', function ($scope, $filter, FloorRestangular) {
// Fetch all objects from the local JSON (see app/models/mdpocket.js)
FloorRestangular.all('floor').getList().then( function(floors) {
// Then select the one based on the view's id query parameter
$scope.floor = $filter('filter')(floors, {floor_id: steroids.view.params['id']})[0];
});
// -- Native navigation
steroids.view.navigationBar.show("Floor: " + steroids.view.params.id );
});
*my view *
<div ng-app="myApp">
<div ng-controller="FloorCtrl">
<div class="topcoat-list__container">
<ul class="topcoat-list">
<li class="topcoat-list__item" hm-tap="open(floor.floor_id)" ng-repeat="floor in floors">
Floor Name: {{ floor.name }}
</li>
</ul>
</div>
</div>
</div>

Resources