Using remote data in AngularJS app - javascript

Can someone ELI5 how to grab json data via GET in Angular, and have it available to a controller -
I'm currently doing this:
app.factory('dataRepo', function($http) {
return {
getData: function() {
return $http.get('/data.json');
}
};
});
app.controller('mainCtrl', function ($scope, $http, _, dataRepo) {
dataRepo.getData().success(function(d) {
$scope.data= d;
console.log($scope.data)
});
console.log($scope.data);
/* more stuff in here using $scope.data
});
The first console.log call logs the data, the second [outside the success function] logs undefined, and writes prior to the other call.
How do I get the data before the app kicks off, or how do I defer the remainder of the code until `$scope.data is populated?? I've looked at promises, and can get the result I need doing what I know is a really dodgy implementation and I don't want to persist with it

U can register a watcher on data and get notified about every change. If data is an array, the third parameter is a boolean watching objects more deeply.
$scope.$watch('data', function(newValue, oldValue) {
}, /*true*/);

Thats because the $http.get call is asynchronous, aka the script continues without calling the callback directly. Therefore the second call to console.log() is executed first and $scope.data is still undefined at this point. Later when the http request is completed the function block inside success() is executed and $scope.data becomes populated.
Although promises make handeling with asynchronous code much easier it doesn't make it synchronous like you'll generally code on the backend.
A solution could be to place the logic which requires the data inside the success() callback.
If you need the data outside the mainCtrl you can use the $watch method like #angabriel suggested, but this code needs to handle de data in all cases. 1: undefined, 2: populated.
3: changed again?
If you're using angular routes to create the controller (and not ng-controller="mainCtrl")
You can use the resolve property to delay the controller/view until the data is loaded shown here

Related

How to show service data immediately when it is received with $http without using angular.copy?

I have a service, which should save me the specific data that I load from a JSON file. I want the data to show on the page as soon as it's received.
I created a $scope variable to be equal to the data in the service, the data is not shown immediately on the page.
I only achieved my goal when using: angular.copy(response.data, this.animals),
but I do not understand why it is not working when I am using: this.animals = response.data. I would like to know why this is happening and what is the difference.
module.service("animalService", function($http) {
this.animals=[];
$http.get('animals.json').then(function(response) {
//this.animals = response.data ----> not working
angular.copy(response.data, this.animals)
});
});
module.controller("animalController", function($scope, animalService) {
//$scope.animals is what I use to create a table in html
$scope.animals = animalService.aniamsl;
});
You are not doing it right, try:
module.service("animalService", function($http) {
return $http.get('animals.json')
});
module.controller("animalController", function($scope, animalService) {
animalService.then(function(res){
$scope.animals = res.data
});
});
any http response returns promise, and what it means is that the data would come asynchronously. As per my understanding using angular.copy triggers a digest cycle and so the changes are detected but it's not at all a good practice. Prefer promise handling as I have shown in the above code
Update:
Since the variable is populated as a promise and it is to be used by other controller , I would suggest to use events such as $rootScope.emit and $rootScope.on so that the controllers are notified about the change in value after $http is completed

Understanding $resource and controllers

I'm trying to understand the following pieces of code. If I understand correctly, below I have three resource objects that interact with REST server data sources (these objects are CategorySrv, ArticleSrv and SearchSrv respectively).
app.factory('CategoryService', function($resource) {
return $resource('categories');
});
app.factory('ArticleService', function($resource) {
return $resource('articles');
});
app.factory('SearchService', function($resource) {
return $resource('articles/search');
});
Now, the question I'd like to ask if when is the controller's code executed? If I loaded the page, it would probably run only once. When will it run the next time? Controller's code doesn't run in a loop, but only when its methods are called from the view, or the portion of the page attached to this controller is loaded (AFAIK).
When this happens, the resource objects listed above are injected into the argument list and controller's function is called:
app.controller('AppCtrl', function($scope, $location, CategoryService, ArticleService, CartService) {
CategoryService.query(function(response) {
$scope.categories = response;
});
ArticleService.query(function(response) {
$scope.articles = response;
});
CartService.get(function(response) {
$scope.cartInfo = response;
});
Do I understand this correctly? Also, what's the difference between get and query above?
Yes. The code won't be running in a loop, only when the controller is loaded.
$resource gives you the option to use get or query, the difference being that the query will immediately return an empty array or object depending on whether isArray is set to true. This helps with rendering so that no errors are thrown when angular expects an array or object but instead gets undefined like you would for async actions. When the response comes back with your data, the digest cycle will run since $scope has changed and it will subsequently re-render your view.
docs: https://docs.angularjs.org/api/ngResource/service/$resource

How to handle $http asynchronous issues in angularjs factory service?

In the factory service js file, I created a service like this:
DashboardFactory.restService = function(method) {
var teststatus = "aaa";
switch (method) {
case "getAll":
$http.get($_REST_API_ROOT_URL + "clients").success(function(data) {
teststatus = data;
}).error(function(error) {
teststatus = 'Unable to load the client data: ' + error.message;
});
teststatus = "bbb";
break;
}
return teststatus;
};
In controller, the code is like this:
$scope.AllClients=DashboardFactory.restService("getAll","","");
I also put the "AllClients" on the html page to monitor the result:
{{AllClients}}
I think the "AllClients" should show the API data from the remote server. But in reality it always give me "bbb".
What should I do?
Thanks in advance!
teststatus gets set as "bbb" and is returned before the API call is finished since the call is asynchronous.
Refer to AngularJS $http call in a Service, return resolved data, not promises for the different ways you can return the data from your API call.
What you want to do is return teststatus at the end of your .success()/.error() functions rather than outside. That way, it only returns once it finishes the API call and sets it to the data returned by the call.
Method 1: You don't NEED to return it at all. You could send in your model to the service RestService.getAllClients = function(myModel), set a property on the model on success myModel.AllClients = data; and then display it with {{myModel.AllClients}}. Setting the property directly on the passed in model will update the binding automagically.
Example: http://jsbin.com/muxijozofa/edit?html,js,output
Method 2: Otherwise you'd need to return the entire get call, which will return a promise, which you'll then need to resolve on the controller as per f.e How do I return data from a $http.get() inside a factory in angularjs
Refactoring tip: Instead of building a "catch-all" rest service filled with switch-case:s, you could build a general rest-service factory which you then implement for each type of call. So you get one method per type of rest call. Instead of DashboardFactory.restService, you'd then call RestService.getAllClients, which sets the data or returns a promise per the methods above.
Switch-case is just bad for feature selection, you're hiding functionality and making the code prone to hidden errors.

Accessing factory properties in AngularJS + Chrome Apps APIs

I'm attempting to build a Chrome App using AngularJS, and one of the abilities I need is to monitor the available network interfaces through chrome.system.network.getNetworkInterfaces. Currently, I am trying to store this data in a factory and inject it into the view controller:
(pared down as much as possible)
Factory:
exampleApp.factory('exampleFactory', ['$http', function ($http) {
var service = {};
service.network_interfaces = 'Detecting network connections...';
chrome.system.network.getNetworkInterfaces(function(interfaces){
service.network_interfaces = interfaces;
})
// This outputs the expected array containing interface data
// in service.network_interfaces
console.log(service)
return service;
}]);
Controller:
exampleApp.controller('MainCtrl', ['$scope', '$http', 'exampleFactory', function($scope, $http, exampleFactory) {
// This outputs the expected array in network_interfaces, identical
// to the console output within the factory
console.log(exampleFactory)
// But then this outputs the initial 'Detecting network connections...'
// string set in the factory
console.log(exampleFactory.network_interfaces)
// And {{network_interfaces}} and {{factory_state}} in the view keep
// 'Detecting network connections...' as the value for network_interfaces
// permanently
$scope.factory_state = exampleFactory;
$scope.network_interfaces = exampleFactory.network_interfaces;
}]);
So:
The factory seems to be returning a good service object, but I'm not sure why exampleFactory and exampleFactory.network_interfaces would have the different states they do between the controller and factory, and especially within the controller itself (regardless of the order they're called in).
I've attempted a lot of different solutions with the hypothesis that it's an asynch issue, but I would think there'd be no appreciable latency on the getNetworkInterfaces method and if there were that everything is set up correctly for Angular to update the {{network_interfaces}} and {{factory_state}} view bindings once data is returned.
I've also tried wrapping various functions in the factory with $rootScope.$apply as a shot in the dark, but with the same results as above.
I've searched around a lot to discover whatever concept it is I've obviously missed, but I think I'm overlooking something fundamental. How to I get the getNetworkInterfaces() data into my controller in a useful state?
Your assumption in #2 is the problem. There will always be a spin of the JavaScript event loop in between a call to an asynchronous method and the invocation of its callback. If there weren't, all sorts of things would subtly break in clients calling the method. This means you are encountering a common problem in Angular development: that you don't get notified that something changed, because the change didn't happen within the context of Angular's digest cycle.
To fix this: try setting up a watch, then calling $scope.apply() within the getNetworkInterfaces() callback. The apply() is guaranteed to happen outside the digest cycle so you shouldn't get an apply-in-apply error.
Alternatively, post a message to yourself when then callback is done. This is better if you're a student of the "if you're using apply() your code is broken" school of thought.
Finally, consider a Promise that you call after the callback. This doesn't quite fit how you've set up your code.
(Also, why call the async method at all in the factory?)
Try watching on network_interfaces to know when it's modified
exampleApp.controller('MainCtrl', ['$scope', '$http', 'exampleFactory', function($scope, $http, exampleFactory) {
// This outputs the expected array in network_interfaces, identical
// to the console output within the factory
console.log(exampleFactory)
// But then this outputs the initial 'Detecting network connections...'
// string set in the factory
console.log(exampleFactory.network_interfaces)
// And {{network_interfaces}} and {{factory_state}} in the view keep
// 'Detecting network connections...' as the value for network_interfaces
// permanently
$scope.factory_state = exampleFactory;
$scope.network_interfaces = exampleFactory.network_interfaces;
$scope.$watch('factory_state.network_interfaces', function() {
console.log($scope.factory_state)
});
}]);

Angular $watch on a recursive service function

I hate asking questions that there's already good info for but I'm missing something to keep my implementation from working.
My scenario is using a recursive function on a service to load my data in iterative chunks. All the data is captured by the scope but only the first set is displayed unless you navigate away and then back again. Clearly, I need to $watch my scope. I just can't figure out how to do so.
AccountService runs a method called getAccountsByPage which is passed an argument of 1. That function then calls itself with a value of 2, and so forth.
$routeProvider.when('/accounts/', {
...
controller: function ($scope, AccountService) {
var accounts = $scope.accounts = AccountService.getAccountsByPage(1);
$scope.$watch('accounts', function(newVal, oldVal) {
console.log(newVal, oldVal);
});
}
});
console outputs: undefined, undefined
[Object, Object, Object...] undefined
To be clear, getting the data isn't the problem. Updating the view is. Angular says not to use $watch on the controller but it seems that everyone does so...
From the code you've posted, $scope.accounts only ever gets set once when the controller first gets instantiated. It doesn't matter what AccountService.getAccountsByPage is doing underneath. Whatever it returns will be put into $scope.accounts, and trigger the $watch that one time. It won't be triggered again until $scope.accounts changes, which I don't see happening anywhere from the code posted.

Categories

Resources