Save scope items on ng-sortable change order - angularjs

I need to change the order of scope, save but me back an error that save() is not a function.
I'm using restangular to create the objects.
The function is triggered Onsort, I tried using http, but also gives me error.
$scope.onChange = function() {
ApiRestangular.all($scope.section).getList($scope.query).then(function(res){
$scope.items = res;
order = ApiRestangular.copy(res);
console.log(order);
$scope.sortOptions = {
animation : 150,
onSort: function(){
order.put().then(function(){
toast.msgToast($scope.section+ ' ...Ordem atualizada!');
});
}
};
});
};

There are a few issues here but I think the biggest one is that "save()" really isn't a function. I believe the correct syntax is $save().
Here is the documentation.
https://docs.angularjs.org/api/ngResource/service/$resource
Without seeing the rest of your controller code it is tough to say what other problems there may be (dependency injection?). But this should get your started.

Related

I am unable to access $rootScope in my controller

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.

Sharing data between controllers and dealing with update/change notifications

I'm trying to get my head around sharing data between multiple controllers, but couldn't find out yet how this is supposed to work (the angular way). I have create a Data service that look something like this:
angular.module('myapp.services')
.service('DataSet', function($rootScope) {
return {
filter: function(filterMethod) {
/// ... do async stuff
$rootScope.$broadcast("Data::filtered");
},
brush: function(brushed) {
/// ... do async stuff
$rootScope.$broadcast("Data::brushed");
},
load: function() {
/// ... do async stuff
$rootScope.$broadcast("Data::loaded");
}
};
});
Next I want to reuse and update data from this service, so I use it in my controller as follows:
angular.module('myapp.controllers')
.controller('FilterCtrl', function ($scope, $rootScope, DataSet) {
$scope.safeApply = function(fn) {
var phase = this.$root.$$phase;
if(phase == '$apply' || phase == '$digest') {
if(fn && (typeof(fn) === 'function')) {
fn();
}
} else {
this.$apply(fn);
}
};
function updateBrushed() {
$scope.safeApply(function() {
$scope.brushed = DataSet.brushed;
});
};
$scope.brushed = [];
$scope.keepSelected = function() {
DataSet.filter(DataSet.FilterMethod.KEEP);
};
$scope.removeSelected = function() {
DataSet.filter(DataSet.FilterMethod.REMOVE);
};
$scope.$on('Data::brushed', updateBrushed);
$scope.$on('Data::filtered', updateBrushed);
});
The problem I have is basically illustrated by the use of the saveApply call. Basically I got this code from here: https://coderwall.com/p/ngisma. What I don't understand though is why I need it. As far as I can see, I'm 'within' $angular when updating the DataSet service. Nevertheless, the view for the Filter controller doesn't get updated without a call to saveApply ($apply doesn't work at all because than I run into the apply already in progress issue).
So, basically my question boils down to: is the approach above a good way to share data, and if so how is notification of changes in the service supposed to work?
Update: Based on Julian Hollman his suggestion I came to the following solution: http://jsfiddle.net/Ljfadvru/7/. This more or less illustrates the full workflow I was working on, though some of it is automatically induced in the fiddle, as opposed to user-interaction based in my real application. What I like about this approach is that it only sends signals when all data is updated.
Working with references, as suggested by Ed Hinchliffe, is nice as well. However, I'm working on a web visualization framework and I'm expecting tens of thousands of items. Clearing arrays and pushing new elements (which seem to me the consequence of this proposal) is really not feasible (if I understand this paradigm well, it would also result in a re-rendering of my vis for every single change). I stand corrected though if there are suggestions for further improvement.
$broadcast doesn't trigger an $apply and I bet your "async stuff" is not $http from angular.
So something happens outside of angular and angular doesn't know that something has changed.
In my opinion the best thing in that case is to write a wrapper for your async code and trigger $apply after date came back from the backend. Don't do it in the controller.
To be honest, I'm not sure quite sure about exactly what is going on with the digest loops in your particular scenario, but I don't think you are approaching this the right way.
The 'angular' way, is to use promises.
Your service should be more like this:
angular.module('myapp.services')
.service('DataSet', function($rootScope) {
return {
filter: function(filterMethod) {
var returnData = []
$http.get('/some/stuff').then(function(data){
for(i in data){
returnData.push(data[i]);
}
});
return returnData;
}
};
});
This sets up an empty placeholder object (returnData) that can be immediately passed to the controller, but a reference is kept so that when the data returns you can retrospectively populate that object. Because the controller and the service reference the same object, it'll 'just work'.
This way you don't have to worry about dealing with $digest or $apply or $broadcast.
You controller can just call $scope.filtered = DataSet.filter();
EDIT
If you want to be able to access the exact same data from multiple controllers:
angular.module('myapp.services')
.factory('DataSet', function($http) {
var cache = {
filtered: []
}
return {
getFiltered: function(){
if(cache.filtered.length) return cache.filtered;
$http.get('/some/url/').then(function(data){
for(i in data){
cache.filtered.push(data[i]);
}
});
}
};
});

Angularjs ng-table with AJAX data source, sorting and filtering

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

angularfireCollection: know when the data is fully loaded

I am writing a small Angular web application and have run into problems when it comes to loading the data. I am using Firebase as datasource and found the AngularFire project which sounded nice. However, I am having trouble controlling the way the data is being displayed.
At first I tried using the regular implicit synchronization by doing:
angularFire(ref, $scope, 'items');
It worked fine and all the data was displayed when I used the model $items in my view. However, when the data is arriving from the Firebase data source it is not formatted in a way that the view supports, so I need to do some additional structural changes to the data before it is displayed. Problem is, I won't know when the data has been fully loaded. I tried assigning a $watch to the $items, but it was called too early.
So, I moved on and tried to use the angularfireCollection instead:
$scope.items = angularFireCollection(new Firebase(url), optionalCallbackOnInitialLoad);
The documentation isn't quite clear what the "optionalCallbackOnInitialLoad" does and when it is called, but trying to access the first item in the $items collection will throw an error ("Uncaught TypeError: Cannot read property '0' of undefined").
I tried adding a button and in the button's click handler I logged the content of the first item in the $items, and it worked:
console.log($scope.items[0]);
There it was! The first object from my Firebase was displayed without any errors ... only problem is that I had to click a button to get there.
So, does anyone know how I can know when all the data has been loaded and then assign it to a $scope variable to be displayed in my view? Or is there another way?
My controller:
app.controller('MyController', ['$scope', 'angularFireCollection',
function MyController($scope, angularFireCollection) {
$scope.start = function()
{
var ref = new Firebase('https://url.firebaseio.com/days');
console.log("start");
console.log("before load?");
$scope.items = angularFireCollection(ref, function()
{
console.log("loaded?");
console.log($scope.items[0]); //undefined
});
console.log("start() out");
};
$scope.start();
//wait for changes
$scope.$watch('items', function() {
console.log("items watch");
console.log($scope.items[0]); //undefined
});
$scope.testData = function()
{
console.log($scope.items[0].properties); //not undefined
};
}
]);
My view:
<button ng-click="testData()">Is the data loaded yet?</button>
Thanks in advance!
So, does anyone know how I can know when all the data has been loaded
and then assign it to a $scope variable to be displayed in my view? Or
is there another way?
Remember that all Firebase calls are asynchronous. Many of your problems are occurring because you're trying to access elements that don't exist yet. The reason the button click worked for you is because you clicked the button (and accessed the elements) after they had been successfully loaded.
In the case of the optionalCallbackOnInitialLoad, this is a function that will be executed once the initial load of the angularFireCollection is finished. As the name implies, it's optional, meaning that you don't have to provide a callback function if you don't want to.
You can either use this and specify a function to be executed after it's loaded, or you can use $q promises or another promise library of your liking. I'm partial to kriskowal's Q myself. I'd suggest reading up a bit on asynchronous JavaScript so you get a deeper understanding of some of these issues.
Be wary that this:
$scope.items = angularFireCollection(ref, function()
{
console.log("loaded?");
console.log($scope.items[0]); //undefined
});
does correctly specify a callback function, but $scope.items doesn't get assigned until after you've ran the callback. So, it still won't exist.
If you just want to see when $scope.items has been loaded, you could try something like this:
$scope.$watch('items', function (items) {
console.log(items)
});
In my project I needed to know too when the data has been loaded. I used the following approach (implicit bindings):
$scope.auctionsDiscoveryPromise = angularFire(firebaseReference.getInstance() + "/auctionlist", $scope, 'auctionlist', []);
$scope.auctionsDiscoveryPromise.then(function() {
console.log("AuctionsDiscoverController auctionsDiscoveryPromise resolved");
$timeout(function() {
$scope.$broadcast("AUCTION_INIT");
}, 500);
}, function() {
console.error("AuctionsDiscoverController auctionsDiscoveryPromise rejected");
});
When the $scope.auctionsDiscoveryPromise promise has been resolved I'm broadcasting an event AUCTION_INIT which is being listened in my directives. I use a short timeout just in case some services or directives haven't been initialized yet.
I'm using this if it would help anyone:
function getAll(items) {
var deferred = $q.defer();
var dataRef = new Firebase(baseUrl + items);
var returnData = angularFireCollection(dataRef, function(data){
deferred.resolve(data.val());
});
return deferred.promise;
}

Reload a list with angularjs

I'm just starting to play with angularJS, so maybe I'm asking something easy to do, but I can't find the way to do it.
The situation is the following: I have a list that's populated by an ng-repeat taking the values from a scoped controller variable. This variable is loaded on page load by an jsonp call, and this works fine.
The problem comes when I need to reload this list based on another select. For example, if a select 'day' value in the select I need to show some values and when I select 'week' I need to show others (also loaded via ajax).
What I've tried is to have a service that loads the data and returns it, and in the controller have two methods, one for the first load and another for the second one that does $scope.apply with the variable. I then call this second method on select value change (I've done it with jquery to simplify it until I can fix this).
This is part of my HTML
<div x-ng-controller='LeaderboardCtrl'>
<select id='leaderboard-select'>
<option value='day'>day</option>
<option value='week'>week</option>
<option value='month'>month</option>
</select>
<div x-ng-repeat='p in leaderboard'>
<p>{{p}}</p>
</div>
</div>
And this is part of the code that affects this functionality
var lead = angular.module("lead",[]);
function LeaderboardCtrl($scope,$attrs,$http,jtlanService) {
$scope.leaderboard = [];
$scope.period = 'day';
var data = {
period:$scope.period
};
$scope.loadLeaderboard = function(){
myService.loadLeaderboard(data).then(function(leaderboard) {
$scope.leaderboard = [];
$scope.leaderboard.push.apply($scope.leaderboard,leaderboard);
});
}
$scope.reloadLeaderboard = function() {
myService.loadLeaderboard(data).then(function(leaderboard) {
$scope.$apply(function() {
$scope.leaderboard = [];
$scope.leaderboard.push.apply($scope.leaderboard,leaderboard);
});
});
}
$scope.loadLeaderboard()
}
lead.service("myService",["$http", function($http) {
var myService = {
loadLeaderboard : function(data) {
var promise = $http.jsonp("/widget/leaderboardJSONP?callback=JSON_CALLBACK&_="+new Date(),{
params:data,
cache:false,
ajaxOptions: { cache: false }
}).then(function(response) {
return response.data;
});
return promise;
}
};
return myService;
}]);
$("#leaderboard-select").change(function(){
scope.period = $("#leaderboard-select").val();
scope.reloadLeaderboard();
});
Here's a fiddle with the code: http://jsfiddle.net/WFGqN/3/
Your fiddle is riddled with issues:
There's no ng-app in your mark-up
You need to change the second Framework Extensions dropdown to one of the "No wrap" options
Your service needs to be defined above your controller
Your controller is referencing "jtlanService" but you've defined "myService"
Your $http.jsonp call isn't going to work as is, but you could use can use the echo service (see Ajax Requests on the left side) to emulate requests
You can't and shouldn't be using jQuery events to call Angular controllers. You should use ng-change and not $().change (and even if you were using jQuery for event binding, you should be using $().on('change')).
You didn't need to use $scope.$apply in your loadLeaderboard function, since when you're calling it, you were already inside of of an $apply call.
There's no need for 2 load+reload leaderboard methods.
And after all that, you don't actually need jQuery.
Here's a fiddle that fixes things up and I think gets you what you want: http://jsfiddle.net/WFGqN/5/. You'll of course need to fix the service on your end, but you get the idea.
I recommend reading this SO answer: "Thinking in AngularJS" if I have a jQuery background?

Resources