this is an extract of a code im working on using ng-table. My problem is really simple and supposedly it should be plain easy to overcome but im just unable to do it.
This is the extract :
$scope.loading++;
clean();
$scope.environment = "SOMEENV";
$http({
method: 'GET',
url: 'http://SOMEIP:SOMEPORT/all?environment=SOMEENV'
}).then(function successCallback(response) {
// this callback will be called asynchronously
// when the response is available
$scope.nodes = response.data;
$scope.chartdataservtype=countservtype(response.data);
$scope.tableParams = new NgTableParams({}, { dataset: response.data.rows });
... more code after that.
Lets says that later on, I want to get the FILTERED DATA, not only of the visible part of the table, but from the WHOLE TABLE. According to their GitHub repo, I should be able to do that so easily as writing :
var filteredData = $scope.tableParams.data;
But the problem is that this approach is giving me ONLY THE VISIBLE rows on the table. And I want the whole set of data.
There is some people saying I can customize the getData function of ng-table ( although on their repo other people is saying is not needed as it is "solved" ) but i dont know how to do that.
Can you guys help with this?
You can access the filtered data including the ngTableEventsChannel service in your controller, and listen for filter changes:
function yourController($scope, NgTableParams, ngTableEventsChannel) {
activate();
function activate() {
ngTableEventsChannel.onAfterDataFiltered(function(tableParams, filteredData){
//DO SOMETHING
});
}
}
In that case, tableParams will be the NgTableParams instance that has changed. And filteredData will be what you want to access, your filtered data (yahoo!).
More info: http://ng-table.com/api-docs/classes/ngtableeventschannel.html
Related
I'm trying to take the response of an $http request and save it to a custom cache. I want to then use that cache to display data into the view. I thought the cache would be checked automatically on each request before fetching new data, but that doesn't seem to be working for me.
The problem I'm having is I can't seem to save the data. The following function needs to make 2 requests: articles and images.
getImages: function() {
var cache = $cacheFactory('articlesCache');
$http.get(posts)
.then(function (data) {
var articles = data;
angular.forEach(articles, function (article) {
var imageId = {id: article.image_id};
$http.post(images, imageId)
.then(function (response) {
article.image = response;
cache.put(article.url, article);
});
});
});
return cache;
}
This creates the custom cache, but there's no data in the returned object. I know now that I can't save the data this way, but I don't know why or how I would go about doing it.
Can anyone explain how storing response data works? Where, if at all, does using promises come in here?
Your return statement executes before the code in your then function does. If you want to return the cache you'll want to run everything through the $q service and then return the resolved promise.
This is probably not the best way to use $cacheFactory. Typically you'd expose your cache as a service at a higher level and then access the cache via the service where needed.
So on your main module you'd have something like this to create the cache.
.factory('cache', function ($cacheFactory) {
var results = $cacheFactory('articleCache');
return results;
})
Then where ever you need the cache you inject it into the controller and use cache.get to retrieve the data from it.
If you want to use $q to implement this, your code would look something like the code below. (Disclaimer: I've never used $q with $cacheFactory like this, so without all of your components, I can't really test it, but this should be close.)
var imageService = function ($http, $q,$cacheFactory) {
var imageFactory = {};
imageService.cache = $cacheFactory('articlesCache');
imageFactory.getImages = function () {
var images = $q.defer();
$http.get(posts)
.then(function (data) {
var articles = data;
angular.forEach(articles, function (article) {
var imageId = {id: article.image_id};
$http.post(images, imageId)
.then(function (response) {
article.image = response;
cache.put(article.url, article);
});
images.resolve(cache.get('articlesCache'))
});
});
return images.promise
app.factory('ImageService', ['$http', '$q', '$cacheFactory', imageService]);
});
I adapted the code from this answer: How to get data by service and $cacheFactory by one method
That answer is just doing a straight $http.get though. If I understand what you're doing, you already have the data, you are posting it to your server and you want to avoid making get call to retrieve the list, since you have it locally.
I have some parameters in the $rootScope as specified below:
myApp.factory('itemService', function($http) {
return $http.get('/items');
});
myApp.run(function($rootScope, itemService) {
itemService.success(function(response) {
$rootScope.items = response;
});
});
myApp.controller('displayCtrl', function($rootScope, $scope) {
$scope.items = $rootScope.items;
});
When I run the above code, I get this error from firebug
TypeError: $rootScope.items is undefined. I really do not know what is happening.
Here is a small addition. items is an array with a list of objects like this:
items = [
{'name': 'spoon', 'price': 200},
{'name': 'table', 'price': 400},
{'name': 'shoe', 'price': 250}
];
I wish to make items available constantly in my app such that I can display each item on the item list (items) without making another request to the server. I intend to achieve this by simply displaying an item using $scope.item = items[$routeParams.id] each time I need to display an item.
I look forward to implement this using either a function attached to ng-click or the normal #/route/:param mechanism.
Thanks
TypeError: $object.property is undefined is usually because a request to a reference of an object is made before that specific object (or its property) has been set. $http requests are asynchroneous by nature so other processes do not get blocked. It should be obvious that trying to make requests synchroneous could cause a major issue for people with very slow connections.
Apart from that, polluting the $rootScope is generally a bad idea. You can find a topic about global variables on the following link so that you investigate why the $rootScope is not such a good place.
Having said all that, it seems to me that you didn't want to make multiple requests to retrieve the same data. If so, you can use the cache option for $http.get methods.
e.g:
myApp.factory('itemService', function($http, $q) {
return {
get: function() {
return $http({
url: 'items.json',
cache: true //keep the result in memory
});
}
};
})
myApp.controller('aCtrl', function(itemService) {
var self = this;
itemService.get().success(function(data) {
self.items = data;
});
});
myApp.controller('bCtrl', function(itemService) {
var self = this;
itemService.get().success(function(data) {
self.items = data;
});
});
This will make sure the information gets requested once and put into a cache. The data is accessible in different places.
<div ng-controller="aCtrl as a">
{{a.items}}
</div>
<div ng-controller="bCtrl as b">
{{b.items}}
</div>
This leaves me with another 'good' practice: the usage of the controllerAs syntax. Which provides a way to use namespaces in AngularJS.
Ofcourse, these are just tips and you should always consider the requirements!
You run asynchronious method at run block :
itemService.success(function(response){
$rootScope.items = response;
});
But initialization goes on, so probably you access $rootScope.items before itemService succeed (or it fails, and you didnt predict such situation). I suggest you to do this (if you want to follow $rootScope convension.. which is bad by the way) :
$rootScope.items = [];
itemService.success(function(response){
$rootScope.items = response;
});
You are setting items in the callback of an asynchronous process, so you are trying to access items on the $rootScope before its actually set.
If you are trying to initialize items when the controller is loaded, then there are other ways to do that such as using the resolve block of a route or manually calling the $http.get on the factory when the controller loads.
Finally, I was able to come up with a solution. I realized that the problem was to have $rootScope.items available in displayCtrl at the same time it loads. But $rootScope.items is available in my view when my html page loads.
So I simply passed the item id as a parameter and obtained it using $routeParams as follows
myApp.controller('displayCtrl', function($routeParams, $scope) {
$scope.item_id = $routeParams.id; //given that the route looks like '/item/:id'
});
Then in my HTML file this what I did
<div ng-bind="items[item_id].name"></div>
<div ng-bind="items[item_id].price"></div>
This actual solved my problem.
I am writing some forms for adding and editing resources. I am using Angucomplete-alt (https://github.com/ghiden/angucomplete-alt) and my own dropdown directive to get choices from the database. Those are in a separate controller from the main form. My regular text fields populate fine on the edit form, but I have issues with the Angucomplete and selects. The scope data is there on page load. I have written a function to grab it using the ID in the URL. But they don't always populate unless you reload the page. How can I get them to populate EVERY time?
Here is my function to populate the form:
$scope.popForm = function(clientId) {
var config = {
params: {clientId: $stateParams.clientId},
headers: {'Content-Type': 'application/x-www-form-urlencoded'}
};
$http.get('assets/php/clients/pop_client.php', config)
.then(function(data) {
var realStatus = data.data.status;
if(realStatus == 'success'){
//Set the $scope from the JSON response
$scope.client = data.data.client;
//Broadcast a bunch of events with data to the dropdowns
$timeout(function() {
$rootScope.$broadcast('setTypeDropdown', data.data.client.type);
$rootScope.$broadcast('setCatDropdown', data.data.client.category);
$rootScope.$broadcast('setTeamDropdown', data.data.client.team);
$rootScope.$broadcast('setA1Dropdown', $scope.client.assigned1);
$rootScope.$broadcast('setA2Dropdown', $scope.client.assigned2);
});
}
});
};
I have another controller that takes care of the selects, so that's why there are broadcasts of the data down to their isolated scopes. Here is one of the $on functions, since they are all basically the same. (There MUST be a less convoluted way of doing this...)
// Options are the select options that I get from the database
// for each instance of the select controller
$scope.$on('setTypeDropdown', function(event, type) {
var i = 0;
$.each($scope.options, function(){
if (this.value == type){
$scope.client.type = $scope.options[i];
}
i++;
});
});
So, IS there a better way of doing this? Because this doesn't quite work...
EDIT: Angucomplete-alts are working well now. Just my convoluted selects to worry about. Wonder if it wouldn't be better to de-Angularize them and not use my directive.
I'm trying to apply the ngTable directive to my Rails app, but can't get it right. I'm still a newbe in angular and this directive seems very poorly documented (though has a great variety of nice examples).
So I have an array called $scope.users containing all the users info I need (e.g. like this), which comes from a $resource query().
And I need to turn it into a table with:
Sorting
Pagination
Filtering from one input (like done over here)
Can someone provide a solution or at least an advice how to put it all together?
UPDATE
So, with great help of wafflejock on the irc channel I've been able to put together some of the functionality. Here's the plunk.
What is still wrong:
The json dataset gets requested every time I change the page, the sorting or anything else. That's a huge amount of requests, so I need it to get cached somehow.
I need to be able to manipulate the dataset from the controller to change the values and remove users if need be. Still have no idea how to implement that.
Hi Almaron (aka Malkav) I'm wafflejock from the IRC here's the thing working as best I could get it:
http://plnkr.co/edit/TUOYmM?p=preview
var app = angular.module('main', ['ngTable']).
controller('DemoCtrl', function($scope, ngTableParams, NameService) {
var data = NameService.data;
$scope.tableParams = new ngTableParams(
{
page: 1, // show first page
count: 10, // count per page
sorting: {name:'asc'}
},
{
total: 0, // length of data
getData: function($defer, params) {
NameService.getData($defer,params,$scope.filter);
}
});
$scope.$watch("filter.$", function () {
$scope.tableParams.reload();
});
});
app.service("NameService", function($http, $filter){
function filterData(data, filter){
return $filter('filter')(data, filter)
}
function orderData(data, params){
return params.sorting() ? $filter('orderBy')(data, params.orderBy()) : filteredData;
}
function sliceData(data, params){
return data.slice((params.page() - 1) * params.count(), params.page() * params.count())
}
function transformData(data,filter,params){
return sliceData( orderData( filterData(data,filter), params ), params);
}
var service = {
cachedData:[],
getData:function($defer, params, filter){
if(service.cachedData.length>0){
console.log("using cached data")
var filteredData = filterData(service.cachedData,filter);
var transformedData = sliceData(orderData(filteredData,params),params);
params.total(filteredData.length)
$defer.resolve(transformedData);
}
else{
console.log("fetching data")
$http.get("http://www.json-generator.com/api/json/get/bUAZFEHxCG").success(function(resp)
{
angular.copy(resp,service.cachedData)
params.total(resp.length)
var filteredData = $filter('filter')(resp, filter);
var transformedData = transformData(resp,filter,params)
$defer.resolve(transformedData);
});
}
}
};
return service;
});
Basically I setup a few functions to do the those long lines to make it a bit easier to read and then setup a cachedData object that I check to see if it's populated before making the call... it looks like it still makes two calls very quickly at the beginning I'm sure you can avoid this by using a flag to check if the data is being loaded already and if so just have it wait instead of redoing the call but not sure it's a huge deal.
The second parameter of $http.get takes a config object - one of the keys it takes is cache, which you can set to true to cache your get request. Another method to reduce requests would be to use _.debounce from lodash or underscore to debounce requests made within a certain time period.
For editing data, in your template, you can have an input with ng-model and a span with a ng-bind (or double curly braces {{ }}) and have an ng-show and ng-hide on the same variable for them for editing.
Second call can be prevented by simple if condition scope.returnListGrid.settings().$scope!=null
I have arrays stored in Firebase, one of which I need to retrieve when a user logs in. Each user has their own array which requires authentication for read. (It would be inconvenient to switch to another data structure). Since $firebase() always returns an object, as per the docs, I'm using the orderByPriority filter. However, if I do simply
$scope.songs = $filter('orderByPriority')($firebase(myref));
that doesn't work as songs always get an empty array.
I don't understand why this happens, but what I've done to solve it is use the $firebase().$on('loaded',cb) form and applied the filter in the callback. Is this a good solution?
The drawback is that I cannot do $scope.songs.$save()
Here's my controller, including this solution:
.controller('songListController', function($scope, $rootScope, $firebase, $filter, $firebaseSimpleLogin){
var authRef = new Firebase('https://my-firebase.firebaseio.com/users'),
dataRef;
$scope.loginObj = $firebaseSimpleLogin(authRef);
$scope.songs = [];
$rootScope.$on("$firebaseSimpleLogin:login", function(event, user) {
// user authenticated with Firebase
dataRef = $firebase(authRef.child(user.id));
dataRef.$on('loaded', function(data){
$scope.songs = $filter('orderByPriority')(data);
});
});
//other controller methods go here
$scope.save = function(){
if (!$scope.loginObj.user)
{
alert('not logged in. login or join.');
return;
}
//Was hoping to do this
//$scope.songs.$save().then(function(error) {
//but having to do this instead:
dataRef.$set($scope.songs).then(function(error) {
if (error) {
alert('Data could not be saved.' + error);
} else {
alert('Data saved successfully.');
}
});
};
});
---Edit in response to Kato's answer---
This part of my app uses Firebase as a simple CRUD json store without any realtime aspects. I use $set to store changes, so I think I'm okay to use arrays. (I'm using jQueryUI's Sortable so that an HTML UL can be re-ordered with drag and drop, which seems to need an array).
I don't need realtime synchronisation with the server for this part of the app. I have a save button, which triggers the use of the $scope.save method above.
The problem with the approach above is that orderByPriority makes a single copy of the data. It's empty because $firebase hasn't finished retrieving results from the server yet.
If you were to wait for the loaded event, it would contain data:
var data = $firebase(myref);
data.$on('loaded', function() {
$scope.songs = $filter('orderByPriority')(data);
});
However, it's still not going to be synchronized. You'll need to watch for changes and update it after each change event (this happens automagically when you use orderByPriority as part of the DOM/view).
var data = $firebase(myref);
data.$on('change', function() {
$scope.songs = $filter('orderByPriority')(data);
});
Note that the 0.8 release will have a $asArray() which will work closer to what you want here. Additionally, you should avoid arrays most of the time.