Understanding $resource and controllers - javascript

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

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

Prevent AngularJS $http promise from returning cached JSON data

In my AngularJS app's signup process, I'm constantly updating a user object.
This may be the wrong way to do it, but I'm using one "signup" controller for all of these signup steps (as the logic in the steps is all very similar and it makes for neater code rather than making a separate "signup1", "signup2" etc controller for each view.
I have a promise that returns userInfo from my database:
.service('userInfo', function ($http, $cookies) {
var userId = $cookies.id;
console.log("UI Cookies", $cookies);
var promise = $http.get('/api/findProfile/' + userId, { cache: false}).
success(function (data) {
var userInfo = data;
console.log("This is fresh data!") // This logs the first time I load my page, but never again
return userInfo;
}).error(function(data) {
console.log("ERROR")
return userInfo
});
return promise;
})
When going from one signup step to the next, I update the profile info on MongoDB then load the next page. If I load the page via changing $location('') from my controller, I get old userInfo that hasn't been updated in my database. However, if I do a full page refresh I get the correct, updated userInfo.
Is this a caching issue? I've tried passing {cache: false} to my $http promise but I'm not getting fresh data (as denoted by the console.log("This is fresh data!"); rather, I'm getting cached data back.
How can I resolve this beyond forcing a full-page reload in Angular?
This is not a caching issue, or at least not the way you think.
Services and factories are singletons in angular, meaning that they get initialized once, before they are first requested to be injected. After that, the service function will not run, all controllers will receive the same object. (In case of a service, the injected object is created by using the service function as a constructor)
If you want to poll for some data every time a view is loaded, you have to initialize the request in a controller (you might do this via a service regardless) - which does get re-initialized without reloading the browser page.
Also, you seem to have some issues with using promises and return values. There's no point in returning from the error and success handlers, as their return value will be discarded. See this discussion for more information on "returning" from async functions.
It's probably easiest if you just return the $http promise from a function in the service, then every controller can handle the promise appropriately, ultimately reducing your service code to this:
.service('userInfo', function ($http, $cookies) {
var userId = $cookies.id;
console.log("UI Cookies", $cookies);
//this function returns the promise returned by $http.get
this.getData = function(){
return $http.get('/api/findProfile/' + userId, { cache: false});
};
})

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)
});
}]);

Using remote data in AngularJS app

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

angularjs passing variables into controller

I have my angular controller setup like most of the examples shown in the docs such that it is a global function. I assume that the controller class is being called when the angular engine sees the controller tag in the html.
My issue is that i want to pass in a parameter to my controller and i don't know how to do that because I'm not initializing it. I see some answers suggesting the use of ng-init. But my parameter is not a trivial string - it is a complex object that is being loaded by another (non-angular) part of my js. It is also not available right on load but takes a while to come along.
So i need a way to pass this object, when it finally finishes loading, into the controller (or scope) so that the controller can interact with it.
Is this possible?
You can use a service or a factory for this, combined with promises:
You can setup a factory that returns a promise, and create a global function (accessible from 3rd-party JS) to resolve the promise.
Note the $rootScope.$apply() call. Angular won't call the then function of a promise until an $apply cycle. See the $q docs.
app.factory('fromoutside', function($window, $q, $rootScope) {
var deferred = $q.defer();
$window.injectIntoAngularWorld = function(obj) {
deferred.resolve(obj);
$rootScope.$apply();
};
return deferred.promise;
});
And then in your controller, you can ask for the fromoutside service and bind to the data when it arrives:
app.controller('main', function($scope, fromoutside) {
fromoutside.then(function(obj) {
$scope.data = obj;
});
});
And then somewhere outside of Angular:
setTimeout(function() {
window.injectIntoAngularWorld({
A: 1,
B: 2,
C: 3
});
}, 2000);
Here's a fiddle of this.
Personally, I feel this is a little bit cleaner than reaching into an Angular controller via the DOM.
EDIT: Another approach
Mark Rajcok asked in a comment if this could be modified to allow getting data more than once.
Now, getting data more than once could mean incremental updates, or changing the object itself, or other things. But the main things that need to happen are getting the data into the Angular world and then getting the right angular scopes to run their $digests.
In this fiddle, I've shown one way, when you might just be getting updates to an Array from outside of angular.
It uses a similar trick as the promise example above.
Here's the main factory:
app.factory('data', function($window) {
var items = [];
var scopes = [];
$window.addItem = function(item) {
items.push(item);
angular.forEach(scopes, function(scope) {
scope.$digest();
});
};
return {
items: items,
register: function(scope) { scopes.push(scope); }
};
Like the previous example, we attach a function to the $window service (exposing it globally). The new bit is exposing a register function, which controllers that want updates to data should use to register themselves.
When the external JS calls into angular, we just loop over all the registered scopes and run a digest on each to make sure they're updated.
In your non-angular JavaScript, you can get access to the scope associated with a DOM element as follows:
angular.element(someDomElement).scope().someControllerFunction(delayedData);
I assume you can find someDomElement with a jQuery selector or something.

Categories

Resources