I am creating a factory for my angular app.
I am asking how to use a method (here setCurrentUser()) in the same factory I am declaring:
app.factory('User', function ($rootScope) {
var User = {
create: function (authUser, username) {
// ...
setCurrentUser(username); // <== ERROR
});
},
// ...
setCurrentUser: function (username) {
$rootScope.currentUser = User.findByUsername(username);
},
// ...
};
return User;
});
Using this.setCurrentUser(username) gives an undefined error, of course...
Please note I need setCurrentUser to be a method of User factory, since I have to use it from controllers (I can't define it to be a local function of User factory...).
What you could do instead is use functions as local vars and return the User object (your public API) like this:
app.factory('User', function ($rootScope) {
var setCurrentUser = function (username) {
$rootScope.currentUser = ...
};
var create = function(authUser, username) {
// ...
setCurrentUser(username); // <== NO ERROR
};
return {
create: create,
setCurrentUser: setCurrentUser
};
});
I'd try to use a closure, since by the time at which you call User.create() User is defined, so you can call setCurrentUser.
User.setCurrentUser(username);
Try to declare setCurrentUser function before create function declaration. If this won't help, you can use this trick:
var that = this;
and in User object call setCurrentUser as:
that.setCurrentUser
Related
I have one service called wd$cache, that is basically a wrapper for localStorage.setItem and get.item.
Now I'm trying to test a controller that uses that service to achieve a certain result. The main problem is that I have an IF statement that gets triggered only if you have localstorage set already which is driving me nuts! (we are doing TDD here)
SERVICE
(function () {
angular
.module('hub')
.controller('promotionNotificationCtrl', promotionNotificationCtrl);
promotionNotificationCtrl.$inject = [
'hub$promotions',
'hub$client',
'wd$cache'
];
function promotionNotificationCtrl(
hub$promotions,
hub$client,
wd$cache) {
var vm = this;
activate();
//////////
function activate () {
hub$promotions.get(hub$client.brand, hub$client.subbrand).success(function (data) {
if (!wd$cache.get('hub$notification')) {
wd$cache.add('before', 123);
} else {
wd$cache.add('after', 321);
}
});
}
}
})();
TEST
describe('The promotion notification controller', function () {
var controller,
hub$client,
$httpBackend,
wd$cache,
mockData = [{
"foo": "bar"
},
{
"faa": "boo"
}];
beforeEach(module('hub'));
beforeEach(module('wired.core'));
beforeEach(module(function ($provide) {
hub$client = {
brand: 'bw',
subbrand: 'plus'
};
wd$cache = {
add: function () {
},
get: function () {
}
};
$provide.value('hub$client', hub$client);
$provide.value('wd$cache', wd$cache);
spyOn(wd$cache, 'add');
}));
beforeEach(inject(function ($controller, _$httpBackend_, _hub$promotions_) {
controller = $controller('promotionNotificationCtrl');
$httpBackend = _$httpBackend_;
hub$promotions = _hub$promotions_;
// request
$httpBackend.expectGET("/umbraco/api/promotions/get/?brand=bw&lang=en&subbrand=plus").respond(200, mockData);
$httpBackend.flush();
}));
it('should attempt to add a cache with a "before" key if no previous "hub$notification" cache was found', function () {
expect(wd$cache.add).toHaveBeenCalledWith('before', 123); //WORKING
})
it('should attempt to add a cache with a "after" key if a previous "hub$notification" cache was found', function () {
localStorage.setItem('hub$notification');
wd$cache.add('hub$notification');
expect(wd$cache.add).toHaveBeenCalledWith('after', 123); // NOT WORKING
// CANT GET THROUGH THE IF STATEMENT
})
});
Basically I can never get to 'Test Cases' after BeforeEach block, whatever I do. I've tried everything, since mocking it to use actual storage.
Any ideas?
You can provide a mock implementation that is already filled with some data:
var cache = {};
beforeEach(module(function ($provide) {
// ...
wd$cache = {
add: function (key, value) {
cache[key] = value;
},
get: function (key) {
return cache[key];
}
};
// add initial data here or in the individual tests, e.g.
// ...
}));
To set up the cache properly for a specific testcase you can use the cache field like this:
cache['hub$notification'] = 'whatever value makes sense here';
Of course you can also do this in beforeEach.
Currently you are trying to do it like this:
wd$cache.add('hub$notification');
expect(wd$cache.add).toHaveBeenCalledWith('after', 123);
This is problematic for two reasons:
You are not updating the cache because you are spying on the add method without .andCallThrough(). You should fix this (add .andCallThrough() after spy creation) otherwise updates from the controller will not affect the cache.
The spy records your call instead. You don't want this for setup code because it makes subsequent assertions more complicated.
Im currently having an issue where when I call a function in a service that changes its own internal variable it doesn't appear to stay unless I change it from a controller. When I output the variable to the console right after I change it, it appears with the correct value. However when I call a function in the controller that prints the object to console it shows the original value.
The variable Im having issues with is "isSignedIn" which always shows false (confirmed with the $scope.verify method in login controller) even after the line "console.log('Signin:' + isSignedIn);" shows true.
Thanks ahead of time for any guidance!
Service Skype
angular.module('Skype', ['ng'])
.service('skypeClient', function () {
//Service Properties
var client = new Skype.Web.Model.Application;
this.errors = [];
this.errorCount = -1;
var isSignedIn = false;
var state = 'SignedOut';
var init = function () {
client.signInManager.state.when('SignedIn', function () {
isSignedIn = true;
console.log('Signin:' + isSignedIn); // This outputs the correct value
});
property = client.signInManager.state;
property.changed(function (status) {
state = status;
console.log("New State"+status) });
}
//Signin Function
var signIn = function (username,password) {
client.signInManager.signIn({
username: username,
password: password
}).then(function () {
isSignedIn = true;
this.errorCount++;
console.log(this.errorCount);
});
}
//SignOut Function
var signOut = function () {
client.signInManager.signOut()
.then(function () {
this.isSignedIn = false;
}, function (error) {
this.erros.push(error);
this.errorCount++;
});
}
return {
signIn: signIn,
signOut: signOut,
init: init,
state: state,
isSignedIn: isSignedIn
};
});
Controller Login
'use strict';
angular.module('login', ['ngRoute', 'classy', 'Metio.Skype'])
.config(['$routeProvider', function ($routeProvider) {
$routeProvider.when('/login', {
templateUrl: 'app/views/login.html',
controller: 'loginCntrl'
});
}]).controller('loginCntrl', function ($scope,skypeClient) {
skypeClient.init();
$scope.skypeClient = skypeClient;
console.log('LoginCntrl Loaded');
$scope.signIn = function () {
skypeClient.signIn($scope.user, $scope.password);
}
$scope.signOut = function () {
skypeClient.signOut();
}
$scope.verify = function () {
console.log(skypeClient);
}
});
[EDIT]
Modified Code according to pdenes recommendations(comments), same issue but cleaner
Factory Skype
.factory('skypeClient', function () {
//Service Properties
var client = new Skype.Web.Model.Application;
var state = 'SignedOut';
//Initialize Listeners
var init = function () {
client.signInManager.state.when('SignedIn', function () {
console.log('Signin:' + state); // This outputs the correct value
});
property = client.signInManager.state;
property.changed(function (status) {
state = status;
console.log("New State" + state);
});
console.log('init');
}
//Signin Function
var signIn = function (username, password) {
client.signInManager.signIn({
username: username,
password: password
}).then(function () {console.log('LoggedIn');});
}
init();
return {
signIn: signIn,
state: function(){return state}
}
});
Controller Login
.controller('loginCntrl', function ($scope,skypeClient) {
$scope.skypeClient = skypeClient;
$scope.signIn = function () {
skypeClient.signIn($scope.user, $scope.password);
}
$scope.verify = function () {
console.log(skypeClient);
console.log($scope.skypeClient);
}
});
DOM Markup
<input type="checkbox">Keep Me Signed In (Currently Signedin: {{skypeClient.state()}} )
Console Output and DOM Changes
When I hit the signin function the console logs "New State SigningIn" and the dom changes to "Currently Signedin: SigningIn" but when I get the next event fired the console logs "New State SignedIn" but the DOM still reflects the old value "Currently Signedin: SigningIn" so the binding only appears to update the first time but not subsequent times.
The value in the object that you return as the service is not the same as the isSignedIn variable you defined earlier. So:
...
var isSignedIn = false;
...
return {
isSignedIn: isSignedIn
// ^this is false when the object is created
// and will remain false, no matter how you
// change the value of the above variable later
};
To expose the value of isSignedIn in your closure from the service, you'd need a function, something like:
...
return {
...
isSignedIn: function () {
return isSignedIn; // <- this refers to the above variable itself
}
};
EDIT: Follow up for the updated code in the question...
So apparently the value is updated properly internally, but the {{skypeClient.state()}} binding is still not updated (only when some other action somehow "forces" it). This is probably happening because the value is updated by something outside of Angular's digest cycle, so there is nothing telling Angular to update things at the right moment.
Looking at the code, state is assigned a new value inside a callback for property.changed (btw, property should properly be declared with var!). Try wrapping that line in an $apply() to make Angular update the bindings!
In place where you call service (factory) value that is returned by function add a $watch. That will update value if it changes in the service.
$scope.$watch(function() { return ModalService.getInfo(); }, function(){
$scope.info = ModalService.getInfo();
});
See solution
The question:
As I understand in sails.js during initialization process Services are initialized before Models.
Is there any possibility to change this behavior? To make Models load before Services.
If it's not, then how can I load particular settings from the database to use them to build instance of my class described in some Service during this Service initialization?
A little bit code for solidity:
api/models/Model.js
console.log("Model Identified");
module.exports = {
attributes: {
name: { type: 'string', required: true, size: 15 },
//Some extra secret fields
}
};
...
api/services/MyCoolService.js
console.log('service inits');
function MyCoolService(options){
//some extraordinary constructor logic may be ommited
}
MyCoolService.prototype.setOptions = function(options){
//Set values for MyCoolService fields.
}
//Some other methods
var myCoolServiceWithSettingsFromDb = new MyCoolService();
//That's the place
model.findOne(sails.config.myApplication.settingsId).exec(function(err,result){
if(!err)
myCoolServiceWithSettingsFromDb.setOptions(result);
});
module.exports = myCoolServiceWithSettingsFromDb;
It's because you instantiate object in service with constructor that needs sails that not exist. Try use this at MyCoolService;
module.exports = {
someOption: null,
method: function () {
var that = this;
sails.models.model.findOne(sails.config.myApplication.settingsId)
.exec(function (err, result) {
if (!err)
that.someOption = result;
});
}
};
that method can be called by sails.services.mycoolservice.method() or simply MyCoolService.method() to give your service some option from DB.
If you want to initiate them at Sails start, call that method at config/bootstrap.js
Thanks to Andi Nugroho Dirgantara,
I ended up with this solution (I still don't like it much, but it works):
api/services/MyCoolService.js
console.log('service inits');
function MyCoolService(options){
//some extraordinary constructor logic may be ommited
}
//All the same as in question
//The instance
var instance;
module.exports = module.exports = {
init: function(options) {
instance = new MyCoolService(options);
},
get: function() {
return instance;
},
constructor: MyCoolService
};
config/bootstrap.js
...
Model.findOrCreate({ id: 1 }, sails.config.someDefaultSettings).exec(function(err, result) {
if (err)
return sails.log.error(err);
result = result || sails.config.someDefaultSettings;
MyCoolService.init(result);
return sails.log.verbose("MyCoolService Created: ", TbcPaymentProcessorService.get());
});
...
tests/unit/service/MyCoolService.test.js
...
describe('MyCoolService', function() {
it('check MyCoolService', function(done) {
assert.notDeepEqual(MyCoolService.get(), sails.config.someDefaultSettings);
done();
});
});
...
It works: the service is instantiated once while bootstraping and it's instance is avaliable everywhere.
But to me this solution still weird... I still don't understand how to globally instantiate instance of my service (for use in a lot of controllers) and make it the best way.
have been using a pretty much loving Crockford's constructor, but, having problems adding scoped functions to the object:
'use strict';
var Constructor = function( params ) {
let config = params,
data = params.datum,
action = function(a,b) { return config.actions[a](b); };
return Object.freeze({
action: action
});
};
var cns = Constructor({
datum: 123,
actions: {
getData: function(b) { return data; }
}
});
cns.action('getData',0);
get Uncaught ReferenceError: data is not defined.
how do I have a function as an argument to the constructor and have that function have the scope of object?
If you are following Crockford's private members in JavaScript post, then getData should be a "privileged" function (a function that has access to private members such as data). Therefore, this function should follow the "privileged" pattern given in his post (JSFiddle example).
var Constructor = function (params) {
var config = params;
var data = params.data;
// Privileged function pattern:
// By using a closure, this method has access to private members.
this.getData = function (b) {
return data;
};
};
// Note: Changed to `new` in order to instantiate the class
var cns = new Constructor({
data: 123
});
console.log(cns.getData(0));
the easiest way seems to be to manually pass object guts to the function, either with call or as an extra argument. since I'm dodging this, am using the extra argument, here self. self is not exposed to the world at large, only to the functions that need to see it.
'use strict';
var Constructor = function( params ) {
let config = params,
data = params.datum,
self = { data: data },
action = function(a,b) { return config.actions[a](b,self); };
return Object.freeze({
action: action
});
};
var cns = Constructor({
datum: 123,
actions: {
getData: function(b,s) { return s.data; }
}
});
cns.action('getData',0);
I have a question regarding definitions of factories in AngularJS. I am unsure if this is a Javascript doubt or AngularJS, but I believe it's Angular.
Say I have a factory definition like this:
angular.module('myApp.services')
.factory('User', function($http) { // injectables go here
var backendUrl = "http://localhost:3000";
var service = {
// our factory definition
user: {},
setName: function(newName) {
service.user['name'] = newName;
},
setEmail: function(newEmail) {
service.user['email'] = newEmail;
},
save: function() {
return $http.post(backendUrl + '/users', {
user: service.user
});
}
};
return service;
});
How is it possible that the function setName is able to set service.user['email'], if service is actually defining setName itself?
This is basic javascript. Forget about the factory for a minute, only consider definition of service.
var service = {
//service will have an empty user js object
user: {},
//a key setName with the value as a function
//which sets a key value pair in user defined above
//service = { user: {name: 'Tony'} }
setName: function(newName) {
service.user['name'] = newName;
},
//a key setEmail with the value as a function
//which sets a key value pair in user defined above
//(consider setName has been called already)
//service = { user: {name: 'Tony', email: 'tony#stark.com'} }
setEmail: function(newEmail) {
service.user['email'] = newEmail;
},
//a key save with the value as a function
//which saves
save: function() {
//return something from a call mimicing $http.post
//return $http.post(backendUrl + '/users', { user: service.user });
}
}
Then, return or use service which has become an API by itself.
Check out dev tool console for log in this FIDDLE
setName function is going to be called by some other code like this,
User.setName("newname "); when this is called the service already defined and it's about setting new value to a property.
and you could use this keyword to make the code more meaningful,
setName: function(newName) {
this.user['name'] = newName;
}
I see one issue with your code, even though you have defined setName function you have the user variable is accessible to outside of the factory,now you can do like this as well,
User.user['name'] = newName;
I think what you need to do is make user a private variable,
angular.module('myApp.services')
.factory('User', function($http) { // injectables go here
var backendUrl = "http://localhost:3000";
var user={};
var service = {
// our factory definition
setName: function(newName) {
user['name'] = newName;
},
setEmail: function(newEmail) {
user['email'] = newEmail;
},
getUser:function(){
return user;
},
save: function() {
return $http.post(backendUrl + '/users', {
user: service.user
});
}
};
return service;
});
What you're describing is a behavior of JavaScript's closures.
Basically, a function has access to the variables outside of the function's scope. If those variables change, it's changed everywhere.
When the functions are created, service is still undefined
The functions create a closure around the service variable
service is then assigned to be the object
All the functions are now pointing to the service object
Here's a way to visualize this:
var service = null;
var getService = function() { return service; };
service = 5;
getService(); // returns 5
service = "service can change";
getService(); // returns "service can change";