AngularJS Factory Event Binding - angularjs

I have an AngularJS directive that uses a factory. What I'm trying to achieve is ensuring that when the 'mousedown' event in the directive is triggered, that a function in my factory is then called.
Seems straightforward, however, where I am running into an issue is in the function in the factory. It doesn't have access to any of my variables that were declared in the factory.
Example binding to the mousedown event in my directive -
$element.on('mousedown', factory.onMouseDown);
Example of my factory and the function that is called by my directive -
angular.module('myApp.myFactory', []).factory('myFactory', [, function () {
var myFactory = function () {
this.someVariable = true;
};
myFactory.prototype.onMouseDown = function (e)
{
console.log(this.someVariable); // this.someVariable comes up as 'undefined'
};
return myFactory;
}]);
What do I need to do in order for my function to be able to access variables in my factory?
Thanks!

With the assumption that myFactory is simulating a "class" and the factory variable holds an object instantiated by myFactory there are many approaches to solve it:
(1) Binding an object as context:
$element.on('mousedown', factory.onMouseDown.bind(factory));
(2) Using a closure to preserve the factory reference:
if callback arguments are known:
$element.on('mousedown', function (evt) {
factory.onMouseDown(evt);
});
if callback arguments aren't known:
$element.on('mousedown', function () {
factory.onMouseDown.apply(factory, arguments);
});
(3) Using a constructor function with privileged methods instead of using the prototype property:
angular.module('myApp.myFactory', []).factory('myFactory', function () {
var myFactory = function () {
var self = this;
self.someVariable = true;
self.onMouseDown = function (e) {
console.log(self.someVariable);
};
};
return myFactory;
});

If the function is correctly triggering, what I think you're missing is what is returned by the myFactory factory. The factory and services in angular act like a closure so you can just declare the variable in the factory and have the function return it since it has access to it.
angular.module('myApp.myFactory', []).factory('myFactory', [function () {
var someVariable = "asdkhdsfhkhlskadfhadsflk";
var myFactory = {
mouseDownEvent: function(e){
console.log(someVariable);
}
}
return myFactory;
}]);

Related

Change the variable value in a service when logout is called

I have a device service and a device controller. First, time whenever the controller is called, I invoke setter method to set a boolean value. I want to change that value when the logout function is called which is in different service.
My Device Service
define([], function () {
'use strict';
var DeviceService = [
"AuthService",
function (AuthService) {
var Device_Create = false;
return {
setUserCreatePermission :function () {
if(AuthService.checkForPermission('Device_Create')){
Device_Create = true;
}
},
getUserCreatePermission : function () {
return Device_Create
}
}
}];
return DeviceService;
})
My Device Controller has a init method which calls the setter method in device service. I have set a scope variable. If it set, it will call the method otherwise not.
define([], function () {
'use strict';
var DeviceListCtrl = ["$rootScope", "$scope", "DeviceService",
function ($rootScope, $scope, DeviceService) {
//variables
$scope.deviceList_init = true;
$scope.Device_Create = false;
init();
if(DeviceService.getDeviceCreatePermission()){
$scope.Device_Create = true;
}
function init() {
if($scope.deviceList_init){
DeviceService.setDeviceCreatePermission();
}
$scope.deviceList_init = false;
}
}]
return DeviceListCtrl;
});
Can someone help me ? TIA. I am new to this
I'm not sure I understood your problem but it seems that you are trying to control two different variables to do the same - one in the controller and other in the service.
If you want to have a variable across different controllers, it should be handled in services/factories.
If you want to notify other controllers that a variable has changed somewhere, you can use $rootScope.$broadcast or $scope.$broadcast depending if it is on the scope or not you need.

In AngularJS Factory or Service, do we need to set a "self" or temporary local variable to remember "myself" or "this"?

Short question is: do we need to use var self = this; in a AngularJS Factory and Service?
I have seen AngularJS code for factory or service that uses a var self = this; to remember itself (the service object), and then inside of the functions of the service object, use self to refer to itself. (kind of like how we are afraid we will lose what this is, so we set self = this to use it later, by the principle of closure.
However, I have found that we can safely use this. Why? Because we get back a singleton service object, and when we invoke myService.getNumber(), the this is bound to the service object myService, so it does work. Example:
If it is a service:
https://jsfiddle.net/yx2s3e72/
angular.module("myApp", [])
.service("myService", function() {
console.log("Entering factory");
this.s = "hello";
this.getLength = function() {
return this.s.length;
};
})
.controller("myController", function(myService) {
console.log("Entering controller");
var vm = this;
vm.s = myService.s;
vm.length = myService.getLength();
});
or if it is a factory:
https://jsfiddle.net/935qmy44/
angular.module("myApp", [])
.factory("myService", function() {
console.log("Entering factory");
return {
s: "hello",
getLength: function() {
return this.s.length;
}
};
})
// controller code not repeated here...
It works too. So is it true that we really don't need to set a var self = this; in a custom AngularJS factory or service?
You may lose the reference to this, therefore the reference is saved in the self var.
angular.module('myApp', [])
.run(function (MyService) {
MyService.method();
})
.service('MyService', function ($timeout) {
this.key = 'value';
this.method = function () {
console.log(this) // service
$timeout(function () {
console.log(this); // window because callback is called from the window
}, 0);
}
});
angular.bootstrap(document.querySelector('#app'), ['myApp']);
See JSFiddle

Jasmine spyOn on function and returned object

I'm using MeteorJS with angular and want to test controller. My controller use $reactive(this).attach($scope). I need to check, if this method was called.
I create something like that for spy:
var $reactive = function(ctrl) {
return {
attach:function(scope) {}
}
};
So I can call it like that:
$reactive('aaa').attach('bbb');
How I can do it in tests?
spyOn($reactive, 'attach');
Doesn't work. I got Error: attach() method does not exist
And how to check if it was called?
This is good call?
expect($reactive).toHaveBeenCalledWith(controller);
And how to check that function attach was called with args (scope)?
You'll need to mock the $reactive component. Replace it with a spy that returns an spyObj in the scope of the test. Then trigger what ever makes the $reactive method to run and test.
var reactiveResult = jasmine.createSpyObj('reactiveResult', ['attach']);
var $reactive = jasmine.createSpy('$reactive').and.returnValue(reactiveResult);
var controller = {};
beforeEach(function () {
module(function ($provide) {
$provide.factory('$reactive', $reactive);
});
module('yourAppModule');
});
it('Should call attach', function () {
$reactive(controller).attach();
expect($reactive).toHaveBeenCalledWith(controller);
expect(reactiveResult.attach).toHaveBeenCalled();
}) ;
You can provide the $reactive spy to the controller dependencies too:
var reactiveResult = jasmine.createSpyObj('reactiveResult', ['attach']);
var $reactive = jasmine.createSpy('$reactive').and.returnValue(reactiveResult);
var ctrl;
beforeEach(inject(function ($controller) {
ctrl = $controller('YourController', {$reactive: $reactive });
}));
it('Should call attach', function () {
//ctrl.triggerThe$reactiveCall
expect($reactive).toHaveBeenCalledWith(ctrl);
expect(reactiveResult.attach).toHaveBeenCalled();
}) ;

Jasmine test for scope methods fails

I have an spec that test's if the method in scope was called (see below)
describe("Event Module tests", function () {
var scope, simpleController;
beforeEach(module('SimpleApplication'));
beforeEach(inject(function ($rootScope, $controller) {
scope = $rootScope.$new();
simpleController = $controller("SimpleController", {
$scope: scope
});
}));
it("Scope function should be triggered", function () {
spyOn(scope, "trigger");
scope.trigger();//invoke the function on controller
expect(scope.trigger).toHaveBeenCalled();//Passes
expect(scope.isTriggered).toBeTruthy();//Fails
});
});
Application Code(Code to be tested):
angular
.module("SimpleApplication", [])
.controller("SimpleController", function ($scope) {
$scope.message = "Hello World";
$scope.isTriggered = false;
$scope.trigger = function() {
$scope.isTriggered = true;
};
});
Jasmine reports that "Expected false to be truthy.". How come ? since the method sets it to true !!
Update:
For some reason, SpyOn was mutating my object to something it was intended for. So below piece of code works good
it("Scope function should be triggered", function () {
scope.trigger();//invoke the function on controller
expect(scope.isTriggered).toBeTruthy();//Now Passes
});
spyOn doesn't call your method. It just spies. If you want it to be called you have to add something:
spyOn(scope, "trigger").andCallThrough()

How to watch a variable defined in a service in angularjs

I am trying to watch changes on an json array defined in an angularj service, but when the change occures, the $watch function is not firing. My controller and service code goes as follows (plunker demo):
var app = angular.module('plunker', []);
app.controller('MainCtrl', function($scope,cityService) {
//$scope.cities = [];
$scope.service = cityService;
cityService.initCities();
$scope.$watch('service.getCity()', function(newVal) {
$scope.cities = newVal;
console.log(newVal)
});
});
app.service('cityService', function($http) {
this.cities = [];
this.initCities = function() {
$http.get('data.js').success(function(data) {
this.cities = data;
});
};
this.getCity = function() {
return this.cities;
};
});
This is because the callback from get set this to window object. Keep the reference of the service in self.
See this plunker
http://plnkr.co/edit/CrgTWRBsg5wi7WOSZiRS?p=preview
I changed several things to make it work:
http://plnkr.co/edit/PDMaEvmx7hG1fKvAmR7R?p=preview
Function watch instead of variable
In the service, removed the keyword this because this has not the same context inside functions.
Return functions in service
Seems ok

Resources