How can I directly inject $http into a provider? - angularjs

All I need to do is to download a json file and assign it to OCategories in PCategory provider after I set the path. However I get an error that $http doesnt exist. How can I inject it into my provider and download inside of the setPath function?
var app = angular.module('NSApp',
[
'ui.bootstrap',
'MDItem',
'MDUser',
'MDNotification',
'MDUpload'
]
);
app.config(function(PCategoriesProvider)
{
PCategoriesProvider.setPath('data/csv/categories.json');
});
MDItem/provider/category.js
angular.module('MDItem').provider('PCategories',function(){
var OCategories;
var OPath;
return{
setPath: function(d){
OPath = d;
console.log('Path is set. Trying to download categories.');
OCategories = $http.get(oPath);
},
$get : function() {
return {
categories : OCategories
}
}
}
});

You can never inject service instances into config functions or providers, since they aren't configured yet. Providers exist to configure specific services before they get injected. Which means, there's always a corresponding provider to a certain service. Just to clarify, here's a little example configuring $location service using $locationProvider:
angular.module('myModule').config(function ($locationProvider) {
$locationProvider.html5Mode(true);
});
So what happens here, is that we configure $location service to use its html5mode. We do that by using the interfaces provided by $locationProvider. At the time when config() is executed, there isn't any service instance available yet, but you have a chance to configure any service before they get instantiated.
Later at runtime (the earliest moment ist the run() function) you can inject a service. What you get when injecting a service is what its providers $get() method returns. Which also means, each provider has to have a $get() function otherwise $injector would throw an error.
But what happens, when creating custom services without building a provider? So something like:
angular.module('myModule').factory('myService', function () {
...
});
You just don't have to care about, because angular does it for you. Everytime you register any kind of service (unless it is not a provider), angular will set up a provider with a $get() method for you, so $injector is able to instantiate later.
So how to solve your problem. How to make asynchronous calls using $http service when actually being in configuration phrase? The answer: you can't.
What you can do, is run the $http call as soon as your service gets instantiated. Because at the time when your service get instantiated, you're able to inject other services (like you always do). So you actually would do something like this:
angular.module('myModule').provider('custom', function (otherProvider, otherProvider2) {
// some configuration stuff and interfaces for the outside world
return {
$get: function ($http, injectable2, injectable3) {
$http.get(/*...*/);
}
};
});
Now your custom provider returns a service instance that has $http as dependency. Once your service gets injected, all its dependencies get injected too, which means within $get you have access to $http service. Next you just make the call you need.
To make your this call is getting invoked as soon as possible, you have to inject your custom service at run() phrase, which looks like this:
angular.module('myModule').run(function (custom, injectable2) {
/* custom gets instantiated, so its $http call gets invoked */
});
Hope this makes things clear.

Since all services are singletons in angular you could simply store a variable in a factory with the $http promise. And then when the factory is called at startup it will download the json.
You can then also expose a method on the factory that refreshes the data.
I know this is not the exact answer to your question, but I thought I'd share how I would do it.
angular.module('MDItem').factory('PCategories', function ($http, PCategoriesPath) {
var service = {
categories: [],
get: function () {
if (angular.isUndefined(PCategoriesPath)) {
throw new Error('PCategoriesPath must be set to get items');
}
$http.get(PCategoriesPath).then(function (response) {
service.categories = response.data;
});
}
};
// Get the categories at startup / or if you like do it later.
service.get();
return service;
});
// Then make sure that PCategoriesPath is created at startup by using const
angular.module('MDItem').const('PCategoriesPath', 'data/csv/categories.json');
angular.module('app').controller('myCtrl', function ($scope, PCategories) {
$scope.categories = PCategories.categories;
// And optionally, use a watch if you would like to do something if the categories are updated via PCategories.get()
$scope.$watch('categories', function (newCategories) {
console.log('Look maa, new categories');
}, true); // Notice the true, which makes angular work when watching an array
})

You have to inject $http in the function $get, because that's the function called by the injector.
However, to download the categories you would be better off using promises:
angular.module('MDItem').provider('PCategories',function(){
var OCategories;
var OPath;
return{
setPath: function(d){
OPath = d;
console.log('Path is set');
},
$get : function($http) {
return {
fetch: function () {
var deferred = $q.defer();
$http.get(oPath).then(function (value) {
deferred.resolve(value);
}
return deferred.promise;
}
}
}
}
});

I implemented what I wanted with a diffrent approach which is quite simple and effective. Just add a dummy controller in the main index.html(NOT PARTIAL). Data is now shared between all my modules and controllers and everything is downloaded once. :) Oh I love AJ.
...
<div ng-controller="initController" hidden></div>
...
initController:
angular.module('NSApp',[]).controller("initController",function($scope, $http, FCategory, FLocation){
$http.get('data/json/categories.json').then(function (response) {
FCategory.categories = response.data;
});
$http.get('data/json/cities.json').then(function (response) {
FLocation.cities = response.data;
});
$http.get('data/json/regions.json').then(function (response) {
FLocation.regions = response.data;
});
});
And now you can access it:
angular.module('MDTest', []).controller("test",function($scope, FCategory, FLocation){
$scope.categories = FCategory.categories;
FCategory factory
angular.module('MDItem').factory('FCategory', function ($http) {
var service = {
categories: [],
....
};
return service;
});

Related

How to wait for load configuration AJAX request to respond and then make other service calls in angularjs

I have a common service which serves configuration details from backend that I am using in other services...is there a way to load the common service before loading other services.
Since I am getting an exception in JS "cannot read property "url" of null". Sometimes It works fine if the configuration loaded successfully before other calls
It sounds like using module.run() would help solve your problem as you can initialise your common service:
var app = angular
.module('demoApp', [])
.run(['commonService', function(commonService) {
commonService.Init()
}]);
Few suggestions,
You can make use module.run function which will execute immediately after module.config function, make AJAX/HTTP call their and keep it in $rootScope/Service, or as angular constant.
Interceptors: you can also make use of the $http's interceptors, so when ever you makes an AJAX request we can configure to execute the interceptor first, so that we can check whether the configs loaded or not, if not then we can grab that first.
If you are using angular routing, then we can make use of resolve convention.
You can set up a service deferred, which holds a promise that gets resolved after initialization of the service is complete. Then when asking for a value of the service return a promise chained to the deferred promise:
angular.module('YourApp').factory("CommonService", function(){
var initDeferred = $q.defer();
var initialize = function() {
/* initialize your service, maybe do something async, maybe set local variables with obtained info
* then resolve deferred promise (maybe with obtained values)*/
initDeferred.resolve(/* initialized service data */);
};
var getUrl = function() {
return initDeferred.promise.then(
function(initializedServiceData) {
return initializedServiceData.url;
}
);
};
return {
initialize: initialize,
getUrl: getUrl
}
});
And don't forget to initialize the service. A good point to do so is the run() function of the module as Brian Giggle already suggested.
angular.module('YourApp', []).run(function(CommonService){
CommonService.initialize();
});
You are able then to retrieve the initialized data as a promise:
angular.module('YourApp').controller('YourController', function($scope, CommonService){
CommonService.getUrl()
.then(function(url){
$scope.url = url;
})
});

passing data from controller to service in angularjs

I am new to angular js.
I need to access data from my controller to service.
I tried using $rooyScope but my service needs to be loaded first and then my controller so using $rooyScope gives me error.
so i am unable to get the data stored in $rooyScope on service.
Can any suggest me a options that suits my expectation
Thank you for your help
Use functions in the service to set a service variable
angular.module('myServiceModule', []).
factory('testService', function() {
var myVariable;
return setVariable: function(parameter) {
myVariable = parameter;
// DO SOMETHING ELSE IF YOU WANT
},
return getVariable: function() {
return myVariable;
}
});
then in your controller inject the service and call the service function
angular.module('myControllerModule', []).
controller('testController, ['testService', function(testService) {
testService.setVariable('TEST');
console.log(testService.getVariable());
}]);

AngularJS: make data received with $http inside factory to be accessible inside controller

I have a factory called "Server" which contains my methods for interaction with the server (get/put/post/delete..). I managed to login and get all data successfully when I had all my code in my controller. Now that I want to separate this code and restructure it a little bit I ran into problems. I can still login and I also get data - but data is just printed; I'm not sure how to access the data in controller? I saw some ".then" instead of ".success" used here and there across the web, but I don't know how exactly.
This is my factory: (included in services.js)
app.factory('Server', ['$http', function($http) {
return {
// this works as it should, login works correctly
login: function(email,pass) {
return $http.get('mywebapiurl/server.php?email='+email+'&password='+pass').success(function(data) {
console.log("\nLOGIN RESPONSE: "+JSON.stringify(data));
if(data.Status !== "OK")
// login fail
console.log("Login FAIL...");
else
// success
console.log("Login OK...");
});
},
// intentional blank data parameter below (server configured this way for testing purposes)
getAllData: function() {
return $http.get('mywebapiurl/server.php?data=').success(function(data) {
console.log("\nDATA FROM SERVER: \n"+data); // here correct data in JSON string format are printed
});
},
};
}]);
This is my controller:
app.controller("MainController", ['$scope', 'Server', function($scope, Server){
Server.login(); // this logins correctly
$scope.data = Server.getAllData(); // here I want to get data returned by the server, now I get http object with all the methods etc etc.
…. continues …
How do I get data that was retrieved with $http within a factory to be accessible in controller? I only have one controller.
Thanks for any help, I'm sure there must be an easy way of doing this. Or am I perhaps taking a wrong way working this out?
EDIT: I also need to be able to call factory functions from views with ng-click for instance. Now I can do this like this:
// this is a method in controller
$scope.updateContacts = function(){
$http.get('mywebapiURL/server.php?mycontacts=').success(function(data) {
$scope.contacts = data;
});
};
and make a call in a view with ng-click="updateContacts()". See how $scope.contacts gets new data in the above function. How am I supposed to do this with .then method?(assigning returned data to variable)
My question asked straight-forwardly:
Lets say I need parts of controller code separated from it (so it doesn't get all messy), like some functions that are available throughout all $scope. What is the best way to accomplish this in AngularJS? Maybe it's not services as I thought …
The trick is to use a promise in your service to proxy the results.
The $http service returns a promise that you can resolve using then with a list or success and error to handle those conditions respectively.
This block of code shows handling the result of the call:
var deferred = $q.defer();
$http.get(productsEndpoint).success(function(result) {
deferred.resolve(result);
}).error(function(result) { deferred.reject(result); });
return deferred.promise;
The code uses the Angular $q service to create a promise. When the $http call is resolved then the promise is used to return information to your controller. The controller handles it like this:
app.controller("myController", ["$scope", "myService", function($scope, myService) {
$scope.data = { status: "Not Loaded." };
myService.getData().then(function(data) { $scope.data = data; });
}]);
(Another function can be passed to then if you want to explicitly handle the rejection).
That closes the loop: a service that uses a promise to return the data, and a controller that calls the service and chains the promise for the result. I have a full fiddle online here: http://jsfiddle.net/HhFwL/
You can change the end point, right now it just points to a generic OData end point to fetch some products data.
More on $http: http://docs.angularjs.org/api/ng.%24http
More on $q: http://docs.angularjs.org/api/ng.%24q
$http.get retuns a HttpPromise Object
Server.getAllData().then(function(results){
$scope.data = results;
})

How to call controller method from service in Angular?

I wonder if I can call controller method from service.
I know that Service is singleton and I can't inject $scope to the service.
In my case I manage Google Maps in service and want to open modal Dialog when user right clicks on Polygon.
As I know, to open/create new instance of dialog, somehow Service must notify controller to do that.
This is a template with controller + method and service: Template
var myApp = angular.module('myApp', []);
function MyCtrl($scope, gridService, $timeout) {
// how to call "foo" method from service?
$scope.foo = function(){
alert('called from service');
};
}
myApp.service('gridService', ['$timeout', function ( $timeout) {
var grid = {
fetching: false,
pristine: true,
pageType: 'Edit'
}
return {
gridSetup: function () {
return grid;
},
setGridSetup: function (newGrid) {
}
}
}]);
Thanks,
The answer is simple: you don't.
The service exists to manipulate data, nothing else. And it really shouldn't care "why" it's doing what it's doing. You ask the service to do something and wait for a response.
Personally I prefer using promises to resolve async operations (i.e. to notify the controller about a state change) since it's heavily supported by many angular services like $http.
But feel free to use callbacks of you wish.
Usually you do not need to call controller from the service - in general the single service could be used by any controller so service shall know nothing about them. In most cases controller calls to the service in react to some user's action and then you need to update view from controller somehow when service did its work (get response from server, etc.). I see following general ways how to do it.
1. Use callbacks.
//controller
$scope.onButtonClick = function() {
$scope.label = "wait, loading...";
function onSuccess(result) {
$scope.label = "done! result is " + result;
}
myService.doSomeWork(param1, param2, onSuccess);
}
//service
doSomeWork: function(param1, param2, onSuccess) {
$.get({...}, onSuccess);
}
So you provide a callback for each action.
2. Subscribe on events
You may use jQuery for events subscribing/triggering
//controller
$(myService).on('update', function() {
$scope.lastUpdateTime = new Date();
});
$scope.onButtonClick = function() {
myService.doUpdate();
}
//service
doUpdate: function() {
$.get({...}, function onOk() {
$(this).trigger('update');
});
}
3. Use promises
A lot of built-in angular services return promise objects, you may use them too:
//controller
myService.doSomething(param1).then(function(result) {
$scope.result = result;
});
//service
doSomething: function(param1) {
return $http.get({...});
}
4. Share some data
An example is $resource service - for example when you call query method it returns empty array-like object that could be safely put to scope and then fills it with values when http request is done.
//controller
$scope.options = myService.options;
$scope.onClick = function() { myService.update() }
//service
options: [],
update: function() {
var self = this;
$http.get({...}).success(function(res) {
self.options.splice(0, self.options.length); //to keep same array
self.options.push.apply(self.options, res.data.options);
});
}
In all these cases services and controllers are separated, services could be used with any controller and you may easily write unit-tests on services that will not break if you change your controller/view part somehow.
A possible solution would be to have a dialog service which you can inject into the grid service. So when the user right clicks on the polygon the handler would call open on the dialog service.
Take a look at the modal service on angular ui as an example.

AngularJS: how do I use an angular service during module configuration time?

See this plunkr for a live example: http://plnkr.co/edit/djQPW7g4HIuxDIm4K8RC
In the code below, the line var promise = serviceThatReturnsPromise(); is run during module configuration time, but I want to mock out the promise that is returned by the service.
Ideally I'd use the $q service to create the mock promise, but I can't do that because serviceThatReturnsPromise() is executed during module configuration time, before I can get access to $q. What's the best way to resolve this chicken and egg problem?
var app = angular.module('plunker', []);
app.factory('serviceUnderTest', function (serviceThatReturnsPromise) {
// We mock out serviceThatReturnsPromise in the test
var promise = serviceThatReturnsPromise();
return function() {
return 4;
};
});
describe('Mocking a promise', function() {
var deferredForMock, service;
beforeEach(module('plunker'));
beforeEach(module(function($provide) {
$provide.factory('serviceThatReturnsPromise', function() {
return function() {
// deferredForMock will be undefined because this is called
// when `serviceUnderTest` is $invoked (i.e. at module configuration),
// but we don't define deferredForMock until the inject() below because
// we need the $q service to create it. How to solve this chicken and
// egg problem?
return deferredForMock.promise;
}
});
}));
beforeEach(inject(function($q, serviceUnderTest) {
service = serviceUnderTest;
deferredForMock = $q.defer();
}));
it('This test won\'t even run', function() {
// we won't even get here because the serviceUnderTest
// service will fail during module configuration
expect(service()).toBe(4);
});
});
I'm not sure I like the solution much, but here it is:
http://plnkr.co/edit/uBwsJxJRjS1qqsKIx5j7?p=preview
You need to ensure that you don't instantiate "serviceUnderTest" until after you've set-up everything. Therefore, I've split the second beforeEach into two separate pieces: the first instantiates and uses $q, the second instantiates and uses serviceUnderTest.
I've also had to include the $rootScope, because Angular's promises are designed to work within a $apply() method.
Hope that helps.

Resources