Can't Inject native angular services into a custom provider - angularjs

I'm trying to create a provider to handle authentication in my app
function Authenticator($http) {
console.log($http);
return {
test: function() {}
}
}
app.provider('authenticator', function AuthenticatorProvider() {
this.config = function () {
var requestParams;
return {
setRequestParams: function (params) {
requestParams = params;
}
}
}();
this.$get = function($http) {
return new Authenticator($http);
};
});
When i run the code above $http is set as undefined. What am i doing wrong? What's the right way to inject $http service into a custom provider?
Thanks

I am guessing what you really want to be doing is something like this:
app.factory('AuthenticationService', ['$http', function($http) {
var AuthenticationService = function() {};
AuthenticationService.prototype.config = function)() { ... }
return new AuthenticationService();
}]);
This creates a service that can be injected into other controllers, directives and services, which there will only ever be a single shared instance of. Fetching the service by its string means the reference inside the function is local to the closure, which means the variable can be safely renamed, saving precious bandwidth.

Related

How can I mock a service that gets immediately invoked when the controller loads?

I have a controller that immediately grabs some data from a service when it loads.
angular.module('app').controller("MyController", function (myService) {
var projectId = myservice.project.id;
});
This data gets set from a previous action in the application. So when karma/jasmine loads this controller in a fresh state this service doesn't have this data. I've tried mocking this service in a beforeEach block but haven't had any luck.
beforeEach(function () {
var myService = {
project: {
id: 'thadsfkasj'
}
}
});
What am I missing?
You can mock your service using angular.value. This should allow you to then inject myService into your controller.
angular.module('myServiceMock', [])
.value('myService',
{
project: {
id: 'thadsfkasj'
}
});
beforeEach(module(
'myServiceMock'
//other modules or mocks to include...
));
Are you using ngMock as well?
If so, you should do the following:
beforeEach(module(function ($provide) {
var myService = {
project: {
id: 'thadsfkasj'
}
}
$provide.value('myService', myService);
}));
Test AngularJS factory function with Jasmine
Without having to deal with injecting $provide or overriding modules, you can simply pass in an object representing your mock when you instantiate your controller in your tests.
beforeEach(inject(function (_$controller_) {
myMockService = {
project: {
id: 100
}
};
ctrl = _$controller_('MyController', {
...
myService: myMockService
...
});
}));

Change value of a variable in a factory AngularJS

Using a factory in AngularJS is it possible to change the TokenRestangular URL value.
for example could I do this:
.factory('projectFactory', ['TokenRestangular', function (TokenRestangular) {
var factory = {
projects: []
};
factory.get = function () {
return
resource = TokenRestangular.all('project');
resource.getList()
.then(function (project) {
factory.project = project;
return factory.project;
})
};
return factory;
}]);
and in my controller change the value of resource i.e.
var projects = projectFactory.get()
projects.TokenRestangular.all('a_different_url');
Hope that makes sense.
It's possible with a service but not with a factory. A service is created as a singleton so each time you inject it you will get the same instance. With a factory you will get a new one.
You should be able to have a simple service like the following and inject it into your controller:
myApp.service('SimpleService', function() {
this.localValue = 0;
this.setLocalValue = function(newValue) {
this.localValue = newValue;
}
});
Un-tested but should give you enough to go on!

update a service variable within an $http callback

I'm using a service to make user data available to various controllers in my Angular app. I'm stuck trying to figure out how to use the $http service to update a variable local to the service (in my case "this.users"). I've tried with and without promises. The server is responding correctly.
I've read several excellent articles for how to use $http within a service to update the scope of a controller. The best being this one: http://sravi-kiran.blogspot.com/2013/03/MovingAjaxCallsToACustomServiceInAngularJS.html. That does not help me though because it negates the benefits of using a service. Mainly, modifying the scope in one controller does not modify throughout the rest of the app.
Here is what I have thus far.
app.service('UserService', ['$http', function($http) {
this.users = [];
this.load = function() {
var promise = $http.get('users.json')
.success(function(data){
// this.users is undefined here
console.log(this.users);
}
};
promise.then(function() {
// this.users is undefined here
console.log('this.users');
});
}]);
Any help is greatly appreciated. Thank you.
Try using
var users = [];
rather than
this.users = [];
and see what
console.log(users);
outputs in each of those cases.
Your service is oddly defined, but if you have a return in it you can access it from any controller:
app.service('UserService', ['$http', function($http) {
var users = [];
this.load = function() {
var promise = $http.get('users.json')
.success(function(data){
// this.users is undefined here
console.log(users);
users = data.data;
}
};
return {
getUsers: function(){
return users;
}
}
}]);
so in your controller, you can use:
var myUsers = UserService.getUsers();
UPDATE to use a service correctly here, your service should return a promise and the promise should be accessed in the controller: Here's an example from another answer I gave
// your service should return a promise
app.service('PickerService', [$http', function($http) {
return {
getFiles: function(){
return $http.get('files.json'); // this returns a promise, the promise is not executed here
}
}
}]);
then in your controller do this:
PickerService.getFiles().then(function(returnValues){ // the promise is executed here as the return values are here
$scope.myDirectiveData = returnValues.data;
});
this does not have scope anymore where you are trying to use it do this instead:
app.service('UserService', [$http', function($http) {
var users = [];
this.load = function() {
var promise = $http.get('users.json')
.success(function(data){
console.log(users);
}
};
promise.then(function() {
console.log(users);
});
}]);
all local variables to a service should just be vars if you assign them to this as a property than they will be included every time the service is injected into a controller which is bad practice.
I think what your asking for is a solution along the lines of defining your service like this:
angular.module('app')
.service('User', function($http, $q) {
var users = null;
var deferred = $q.defer()
return {
getUsers: function() {
if(users) {
deferred.resolve(users);
} else {
$http.get('users.json');
.success(function(result) {
deferred.resolve(result);
})
.error(function(error) {
deferred.reject(error);
});
}
return deferred.promise;
}
};
});
Then in one Each controller you would have to do this:
angular.module('app')
.controller('ACtrl', function($scope, User) {
User.getUsers().then(function(users) {
// Same object that's in BCtrl
$scope.users = users;
});
});
angular.module('app')
.controller('BCtrl', function($scope, User) {
User.getUsers().then(function(users) {
// Same object that's in ACtrl
$scope.users = users;
});
});
NOTE: Because the deferred.promise the same promise passed to all controllers, executing deferred.resolve(users) in the future will cause all then success callbacks in each of your controllers to be called essentially overwriting the old users list.
All operations on the list will be noticed in all controllers because the users array is a shared object at that point. This will only handle updates to the user list/each individual user on the client side of your application. If you want to persist changes to the server, you're going to have to add other $http methods to your service to handle CRUD operations on a user. This can generally be tricky and I highly advise that you check out ngResource, which takes care of basic RESTful operations

Unit Testing AngularJS and PouchDB Service

I am attempting to unit test my individual Angular factories but am having a hard time trying to correctly mock and inject the PouchDB object. My factory code is currently as follows:
factory('Track', [function() {
var db = new PouchDB('tracks');
var resource = {
getAll: function() {
return db.allDocs({include_docs: true});
}
return resource;
}]);
I had tried to use Angular's $provide service to inject a mock PouchDB instance with no luck:
module(function($provide) {
$provide.value('PouchDB', {
allDocs: function() {
return 'MOCKED';
}
});
I am not entirely sure where to go from here. Any help would be greatly appreciated!
As just stated in the comments:
you have to wrap the global variable PouchDB inside a service to make it injectable. This is due to Angular doing DI via simple function-parameters. So just do something like:
angular.module('myModule')
.factory('PouchDBWrapper', function(){
return PouchDB;
}
Then you can inject it into your Track factory:
factory('Track', [function(PouchDBWrapper) {
var db = new PouchDBWrapper('tracks');
var resource = {
getAll: function() {
return db.allDocs({include_docs: true});
}
return resource;
}]);
and in your test you can mock it by:
module(function($provide) {
$provide.factory('PouchDBWrapper', {
allDocs: function() {
return 'MOCKED';
}
});

Mocking Cookies with Angular

In my main describe I have the following:
beforeEach(inject(function(...) {
var mockCookieService = {
_cookies: {},
get: function(key) {
return this._cookies[key];
},
put: function(key, value) {
this._cookies[key] = value;
}
}
cookieService = mockCookieService;
mainCtrl = $controller('MainCtrl', {
...
$cookieStore: cookieService
}
}
Later on I want to test how a controller believes if the cookie already exists, so I nest the following describe:
describe('If the cookie already exists', function() {
beforeEach(function() {
cookieService.put('myUUID', 'TEST');
});
it('Should do not retrieve UUID from server', function() {
expect(userService.getNewUUID).not.toHaveBeenCalled();
});
});
However when I'm making the change to cookieService it's not persisting into the controller being created. Am I taking the wrong approach?
Thanks!
EDIT: Updated the testing code and this is how I'm using $cookieStore:
var app = angular.module('MyApp', ['UserService', 'ngCookies']);
app.controller('MainCtrl', function ($scope, UserService, $cookieStore) {
var uuid = $cookieStore.get('myUUID');
if (typeof uuid == 'undefined') {
UserService.getNewUUID().$then(function(response) {
uuid = response.data.uuid;
$cookieStore.put('myUUID', uuid);
});
}
});
Your unit tests need not have to create a mock $cookieStore and essentially re-implement its functionality. You can use Jasmine's spyOn function to create a spy object and return values.
Create a stub object
var cookieStoreStub = {};
Set up your spy object before creating the controller
spyOn(cookieStoreStub, 'get').and.returnValue('TEST'); //Valid syntax in Jasmine 2.0+. 1.3 uses andReturnValue()
mainCtrl = $controller('MainCtrl', {
...
$cookieStore: cookieStoreStub
}
Write unit tests for the scenario in which cookie is available
describe('If the cookie already exists', function() {
it('Should not retrieve UUID from server', function() {
console.log(cookieStore.get('myUUID')); //Returns TEST, regardless of 'key'
expect(userService.getNewUUID).not.toHaveBeenCalled();
});
});
Note: If you'd like to test multiple cookieStore.get() scenarios, you might want to move the creation of the controller into a beforeEach() inside the describe() block. This allows you to call spyOn() and return a value appropriate for the describe block.

Resources