I am new to AngularJS and have a service that loads my initial user configuration
angular.module('myApp').service('myService', ['$http', function ($http) {
var self = this;
self.user = {};
self.loadConfiguration = function () {
$http.get('/UserConfig').then(function (result) {
self.user = result.data;
});
};
self.loadConfiguration();
}]);
I have a controller that uses the configuration from this service
angular.module('myApp').controller('myController', ['$scope', 'myService', function ($scope, myService) {
var self = this;
// calculation based on service value
self.something = myService.user.something * something else;
}]);
The problem here is that myService.user.something may be undefined since the AJAX request may not have completed when this code is called. Is there a way to have the service complete before any other code is run? I want the service function 'loadConfiguration' to be run only once irrespective of the number of controllers that depend on it.
You can call your service method inside .run() function
Run Blocks
Run blocks are the closest thing in Angular to the main
method. A run block is the code which needs to run to kickstart the
application. It is executed after all of the service have been
configured and the injector has been created. Run blocks typically
contain code which is hard to unit-test, and for this reason should be
declared in isolated modules, so that they can be ignored in the
unit-tests.
https://docs.angularjs.org/guide/module
angular.module('myApp').run(function()){
//use your service here
}
One way to deal with ajax delay, is use $rootScope.$broadcast() function on $http.success which will broadcast your custom event to all controllers. Antoher way is to use promises and perform actions in controllers after resolve. Here are some ideas: https://groups.google.com/forum/#!topic/angular/qagzXXhS_VI/discussion
If you want to make sure that your code in controller gets executed after your AJAX call returns, you may use events.
Use this in your service:
angular.module('myApp').service('myService', ['$http', '$rootScope', function ($http, $rootScope) {
var self = this;
self.user = {};
self.loadConfiguration = function () {
$http.get('/UserConfig').then(function (result) {
self.user = result.data;
$rootScope.$broadcast('myService:getUserConfigSuccess');
});
};
self.loadConfiguration();
}]);
In your controller:
angular.module('myApp').controller('myController', ['$scope', 'myService', function ($scope, myService) {
var self = this;
$scope.$on('myService:getUserConfigSuccess', function() {
// calculation based on service value
self.something = myService.user.something * something else;
})
}]);
You can even attach an object to the event.
Please refer to https://docs.angularjs.org/api/ng/type/$rootScope.Scope .
Related
Im trying to make a AngularJS factory that provides my app with a list of businesses. But i cant seem to get the variable the first time this is run. But after interval is run, i get the variable.
I get this on the first run on the controller in the page:
angular.js:12783 Error: [$injector:undef] Provider 'businessList' must return a value from $get factory method
But I think my solution is faulty, any how? Can anyone point me in the right direction here? For example is using rootScope here a good idea?
What I want is a globally accessible list of businesses in my app, that is collected on start of visit, and updates itself with a timer. So i dont have to all the time call for induvidial requests from the laravel backend, when i can just find it in that list.. is my idea.
Factory:
myApp.factory('businessList', ['$interval', '$http', '$rootScope',
function($interval, $http, $rootScope) {
function bedriftliste() {
$http.get('get/allebedrifter')
.then(function(result) {
bedrifter = result.data;
$rootScope.bedrifter = bedrifter;
});
return $rootScope.bedrifter;
}
var bedrifter = bedriftliste();
// start periodic checking
$interval(bedriftliste, 5000);
return bedrifter;
}
]);
Controller
myApp.controller('bsC', ['$rootScope', '$scope', 'businessList',
function($rootScope, $scope, businessList) {
$scope.allebedrifter = businessList;
}]);`
I solved this by just doing a http.get if object was null.
if (!$rootScope.allebedrifter) {
$http.get('get/bedrift/' + $scope.tslug)
.then(function(result) {
bedriften = result.data;
$scope.bedriften = bedriften;
});
Seems to work fine like this
Although I am late in pointing out but that doesn't seem to be a proper solution to this problem. You need to make these changes in factory:
myApp.factory('businessList', ['$interval', '$http', '$rootScope',
function($interval, $http, $rootScope) {
function bedriftliste() {
return $http.get('get/allebedrifter');
}
}
]);
and in the controller you'll do something like this:
myApp.controller('bsC', ['$rootScope', '$scope', 'businessList', function($rootScope, $scope, businessList) {
function TestFunction(){
businessList.bedriftliste().then(function successCallback(response) {
$scope.allebedrifter = response.data;
//it will invoke 5 seconds after you receive the response from your factory method, I didn't test it but it will solve your problem
$interval(TestFunction, 5000);
}, function errorCallback(response) {
});
}
}]);
I am facing following problem:
In my AngularJS application, I have service, which includes data and a function, which refreshes them
angular.module('testService', ['ui.router'])
.factory('testService', function($http) {
var service = {};
var _baseUrl = 'http://127.0.0.1:8000/api/v1/test/';
service.orders = {created: 0, approved: 0};
/*
* get number of unprocessed orders
*/
service.updateNOrders = function(){
var tableName = 'orders?stats=true';
$http({
method: 'GET',
url: _baseUrl + tableName,
}).success(function(data){
service.orders = data;
console.log(data);
return;
}).error(function(err){
alert(JSON.stringify(err));
return;
})
}
});
I can inject this service to i.e. directive and call the function updateNOrders to update the data from the directive.
What I want, though, is for service to call this function every n seconds.
The service is shared in several directives and instead of every directive, taking care of updating the data, I would like the service to do it itself.
I have tried comething like:
angular.module('testService', ['ui.router'])
.factory('testService', function($http, $q, $rootScope, $interval) {
$interval(console.log('test'),1000);
/*
rest of the code
*/
});
but that does not work.
So - Is it possible to call the function updateNOrders inside testService every minute or so?
Creating service with side effect is the bad idea. Better off create service with method which sets your interval with callback. Then you can inject your service in run callback (run calls callback only once):
angular.module('testService', ['ui.router'])
.factory('testService', ['$interval', function($interval) {
var service = {};
service.doPeriodicAjaxRequest = doPeriodicAjaxRequest;
function doPeriodicAjaxRequest() {
$interval(yourCode, ...);
}
return service;
}]);
Then:
angular.module('', [])
.run(['testService', function (testService) {
testService.doPeriodicAjaxRequest();
}]);
Why dont you call the service function from controller in certain interval of time.
$interval(testService.functionName,1000);
I have a basic data Service which will be used across Controllers. But I'm having an issue grabbing some data that's been added via $http.
Service:
angular.module('core').service('FormService', ['$http', function($http) {
var _this = this;
_this.dropdownData = {
contactTimes: ['Anytime','Morning','Afternoon','Evening'],
industries: {},
};
$http.get('/json').success(function(resp){
_this.dropdownData.industries = resp.industries;
});
}]);
Controller:
angular.module('core').controller('SignupController', ['$scope', '$http', '$state', 'FormService', function($scope, $http, $state, FormService) {
console.log(FormService.dropdownData); // Shows full object incl industries
console.log(FormService.dropdownData.industries); // empty object {}
}]);
How do I get FormService.dropdownData.industries in my controller?
Create a service like below
appService.factory('Service', function ($http) {
return {
getIndustries: function () {
return $http.get('/json').then(function (response) {
return response.data;
});
}
}
});
Call in controller
appCtrl.controller('personalMsgCtrl', ['$scope', 'Service', function ($scope, Service) {
$scope.Industries = Service.getIndustries();
}]);
Hope this will help
Add a method to your service and use $Http.get inside that like below
_this.getindustries = function (callback) {
return $http.get('/json').success(function(resp){
_this.dropdownData.industries = resp.industries;
callback(_this.dropdownData)
});
};
In your controller need to access it like below.
angular.module('core').controller('myController', ['$scope', 'FormService', function ($scope, FormService) {
FormService.getDropdownData(function (dropdownData) {
console.log(dropdownData); // Shows full object incl industries
console.log(dropdownData.industries); // object {}
});
} ]);
Given that your console log shows the correct object, that shows your service is functioning properly. Only one small mistake you have made here. You need to access the data attributes in your return promise.
angular.module('core').service('FormService', ['$http', function($http) {
var _this = this;
_this.dropdownData = {
contactTimes: ['Anytime','Morning','Afternoon','Evening'],
industries: {},
};
$http.get('/json').success(function(resp){
//note that this is resp.data.industries, NOT resp.industries
_this.dropdownData.industries = resp.data.industries;
});
}]);
Assuming that you're data is indeed existing and there are no problems with the server, there are quite a few possible solutions
Returning a promise
angular.module('core').service('FormService', ['$http', function($http) {
var _this = this;
_this.dropdownData = {
contactTimes: ['Anytime','Morning','Afternoon','Evening'],
industries: {},
};
_this.dropdownData.industries = $http.get('/json');
}]);
//Controller
FormService.industries
.then(function(res){
$scope.industries = res.industries
});
Resolving with routeProvider / ui-route
See: $http request before AngularJS app initialises?
You could also write a function to initialize the service when the application starts running. At the end of the day, it is about waiting for the data to be loaded by using a promise. If you never heard about promises before, inform yourself first.
The industries object will be populated at a later point in time when the $http call returns. In the meantime you can still bind to the reference in your view because you've preserved the reference using angular.copy. When the $http call returns, the view will automatically be updated.
It is also a good idea to allow users of your service to handle the event when the $http call returns. You can do this by saving the $promise object as a property of industries:
angular.module('core').service('FormService', ['$http', function($http) {
var _this = this;
_this.dropdownData = {
contactTimes: ['Anytime','Morning','Afternoon','Evening'],
industries: {},
};
_this.dropdownData.industries.$promise = $http.get('/json').then(function(resp){
// when the ansyc call returns, populate the object,
// but preserve the reference
angular.copy( resp.data.industries, _this.dropdownData.industries);
return _this.dropdownData.industries;
});
}]);
Controller
app.controller('ctrl', function($scope, FormService){
// you can bind this to the view, even though the $http call has not returned yet
// the view will update automatically since the reference was preserved
$scope.dropdownData = FormService.dropdownData;
// alternatively, you can hook into the $http call back through the $promise
FormService.dropdownData.industries.$promise.success(function(industries) {
console.log(industries);
});
});
I've a basic controller and inside I've a global function that is called from a different controller, which works absolutely fine in a running application.
Now does anybody know how to unit test this global function?
the cntrl it contains the function
apmstore.controller('HeaderCtrl', function(authentication, $rootScope, $scope, loginService){
// how to test this function called in the loginCtrl below
$rootScope.setDropDownStatus = function(visID, user, userImg) {
$scope.dropdown = visID;
$scope.user = user;
$scope.imgUrl = userImg;
};
});
loginCtrl
apmstore.controller('loginCtrl', function($scope, authentication, loginService,
$rootScope) {
authentication.isAuthenticated = false;
//Here is where its called, i want to unit test this
$rootScope.setDropDownStatus(false, null, null);
$scope.templates = [ {url : '/login'}, {url : '/config'} ];
$scope.login = function() {
loginService.login($scope);
}
});
Do I need to change the logic in HeaderCtrl to make it easier to test, i.e. decouple into a service/factory etc?
Anyone know how to tackle this? Thanks
Try this:
it('should call setDropDownStatus ', inject(function($rootScope, $controller, authentication, loginService) {
$scope = $rootScope.$new();
//fake the setDropDownStatus function as we don't care where this function is created
//We only care about whether this function is called.
$rootScope.setDropDownStatus = function(){
}
spyOn($rootScope, "setDropDownStatus");
////////
ctrl = $controller('loginCtrl', {
$scope: $scope,
authentication: authentication,
loginService: loginService,
$rootScope: $rootScope
});
//verify that this function is called with expected parameters.
expect($rootScope.setDropDownStatus).toHaveBeenCalledWith(false,null,null);
}));
DEMO
Yes to your last question. You shouldn't be defining a function in one controller and using it in another. Put it in a service and inject the service wherever needed.
I'm trying to test a controller in my Angular framework:
.controller('UserCtrl', ['$scope', '$location', 'User', 'Config',
function ($scope, $location, User, Config) {
...
}])
This controller depends on a couple of Services that require the $http object to make server calls:
.factory('User', ['$http', function ($http) {
var data = {};
return {
query: function(oper, putdata, callback){
if(oper == 'get'){
$http.get(getUrl("user",null)).success(function(data2) {
console.log(data2);
callback(data2);
data.userinfo = data2;
});
},
userinfo: data
};
}])
But when I try bootstrapping the controller I can't get httpBackend to work:
describe('UserCtrl', function(){
var ctrlScope, ctrl, $httpBackend, controllerService;
beforeEach(
inject(function($httpBackend, $http, $rootScope, $controller, User, Config) {
_User = User;
_Config = Config;
spyOn(User, 'getUserInfo').andCallThrough();
//spyOn(User, 'query').andCallThrough();
ctrlScope = $rootScope.$new();
controllerService = $controller;
httpMock = $httpBackend;
})
);
it('should create setup userinfo object ', function() {
httpMock.expectGET("/user/default/details").
respond({somejson});
ctrl = controllerService('UserCtrl', {$scope: ctrlScope, $location: location, User: _User, Config: _Config});
expect(_User.getUserInfo).toHaveBeenCalled();
httpMock.flush();
expect(ctrlScope.userinfo.length).toBe(1);
});
});
All I ever get is:
Error: No pending request to flush !
so is it possible to use httpBackend with a service that you have called from a controller you are testing?
Is there a reason you want to specifically test for a GET request? This is an implementation detail of your User service.
Regardless of whether your function is called query() or getUserInfo(), I suspect all that you really want to know is that the function was called on your User service. It's up to the User service to have it's own set of tests that will test that a GET request was made to /user/defaults/details.
The reasoning behind this is that you don't want unrelated unit tests to break when you change the implementation of your User service. What happens when your API changes to /user/query? Your UserCtrl tests should not break, nor should any other controller that's using User service. If your UserCtrl is calling getUserInfo() on User service, it's doing the right thing as far as you're concerned. Your User service unit tests will break and once you fix them to point to your new URL, everything is back in order.