Updating a 'this' value in a service via a function - javascript

I'm quite new to Angular and am trying to understand how everything works. I've been poking around and couldn't find any information on how to do this. So, I've got a service that defines
this.totalCount = 0;
In my controller, my get request retrieves some emails and then executes a function called addMessage for each message it retrieves. The addMessage function is in my service.
The function in my service looks like this:
this.addMessage = function (messageObj) {
this.messagesList.push(messageObj);
}
Basically, I am trying to increment this.totalCount each time this function is executed so that it will update and then can be displayed in the view. I have it displaying in the view currently, however its number always remains 0.
I've tried the following:
1.
this.addMessage = function (messageObj) {
this.messagesList.push(messageObj);
this.totalCount++;
}
2.
var count = this.totalcount
this.addMessage = function (messageObj) {
this.messagesList.push(messageObj);
count++; //and then attempted to display this value in the view but with no luck
}
Any tips would be greatly appreciated.

try this:
var that = this;
this.addMessage = function (messageObj) {
that.messagesList.push(messageObj);
}

I assume that you're binding the var this way in your controller and your view
Service :
this.totalCount = 0;
this.totalCount++;
Controller :
$scope.totalCount = service.totalCount;
view :
{{totalCount}}
And if you're actually doing it like this, you should face this kind of trouble.
The main problem is that totalCount is a primitive var and doing this.totalCount++ will break the reference. If you want to keep some var you should bind it as a sub-object.
This way :
Service :
this.utils = {};
this.utils.totalCount = 0;
this.utils.totalCount++;
Controller :
//This is the most important part. You bind an object. Then even if you loose the totalCount reference, your object will keep its own reference.
$scope.myServiceUtils = service.utils;
View :
{{myServiceUtils.totalCount}}
Actually in service (it's a matter of taste) i prefer a lot to use the object syntax instead of this (as "this" can be confusing)
This way :
var service = {};
service.utils.totalCount = 0;
service.addItem = function(){
...
}
return service;
Hope that was your issue.

You pass argument to another function which has different scope than your service. It is trick with assigning current object to variable, which is visible from function.
var that = this;
this.addMessage = function (messageObj) {
that.messagesList.push(messageObj);
that.totalCount++;
}
Should work.
So you assign that variable with current object, which is visible in inner function scope.
In a function addMessage body, this refers to function scope which is new, and there is no compiler error, but messagesList is a null object and totalCount is incremented, but after program leave function, it's not visible in service, because it is in a function scope which isn't assigned to any variable.
To update service variable as it changes in your controller, use $watch.
$scope.$watch(function() {
return messagesService.totalCount;
}, function(new,old){
$scope.totalmessagecount = messagesService.totalCount;
});
First parameter of $watch if function which return observed for change element. Another is standard function to perform operation after update.

Related

problems with angular factory

I'm using angular factory to share data between controller each controller is for one page. Here is my js file
app.factory('myService', function() {
var savedData = {};
function set(data) {
savedData = data;
}
function get() {
return savedData;
}
return {
set: set,
get: get
}
});
app.controller("logincont", ['$scope','$http','md5','$window','myService',function($scope,$http,md5,$window,myService){
$scope.cari = function () {
$http.get('http://localhost:8089/MonitoringAPI/webresources/login?a='+$scope.userid+'&d='+$scope.password).then(function(response){
$scope.reslogin = response.data;
$scope.reslogin2 = response.data.username;
myService.set($scope.reslogin2);
console.log($scope.reslogin2);
console.log(myService.set($scope.reslogin2));
});
};
}]);
app.controller("moncont", ['$scope','$http','$filter','myService',function($scope,$http,$filter,myService){
$scope.user = myService.get();
console.log($scope.user);
}]);
Here is the result when I call console.log
console.log($scope.reslogin2) = ristian
console.log(myService.set($scope.reslogin2)) = undefined
console.log($scope.user)={}
The result that I expected, ristian is filled each scope.
There are a lot of issues here, some have already been addressed in the comments. Another of the problems is that your set() function overrides the savedData object reference. So when you call myService.get() you get the reference to the empty object, then when the http request resolved and set is called, whatever you've assigned originally, still references the empty object.
So in the above example, this is what happens chronologically (assuming you call $scope.cari() at some point in time):
$scope.user = myService.get(); assigns a reference to the empty object in savedData to $scope.user
At some point you make a http request, that calls myService.set($scope.reslogin2);
This call to set overrides the reference in savedData.
$scope.user still references the old empty object.
To fix this particular issue, you need to either rethink your entire flow, or replace your set method with somethink like this
function set(data) {
angular.extend(savedData, data);
}
which mutates the savedData object, instead of overrides it. This can cause other issues, with empty properties not being overriden, but that entirely depends on what properties are in data.

Jasmine test private callback function

I know that calling private functions directly in unitTests is not a good practice and we must test the private code trough public methods.
I'm in a case that I don't know what to do to achieve what I want. I want to know if a callback function has been called from my interval. This is implemented in an angular controller.
function prepareInterval() {
self.callbacksData = [];
if(self.DynamicValuesList !== null) {
self.myPromise = $interval(callbackFunction, userInputInterval * 1000);
}
}
and my callback function only shows the data from the callbackFunction. I want to unitTest if that callbackFunction has been called but I can't.
I tried https://makandracards.com/makandra/32477-testing-settimeout-and-setinterval-with-jasmine
it('myUnitTest', function(){
//Prepare data
var controller = createController();
spyOn(controller, 'callbackFunction');
expect(controller.callbackFunction).not.toHaveBeenCalled();
});
The error that I'm getting is.
callbackFunction() method does not exist
EDIT: By the way I'm injecting the angular mock in the beforeEach function
I would take a slightly different approach here.
Obviously you don't want the callbackFunction itself to be exposed, so don't. Keep it private.
You do, however, return it as a value to your self instance.
self.myPromise = $interval(callbackFunction, userInputInterval * 1000);
So what you cán test, is that self.myPromise value. If that value is containing the interval, your interval has been set and thus you can be pretty sure your method has been called.
I expect that self object to be the controller, so you can just test the value of self.myPromise like this:
it('myUnitTest', function(){
//Prepare data
var controller = createController();
expect(controller.myPromise).toBe( /* undefined? */);
});
update
Just to test the interval value > 0:
You can try to refactor for testability. I'm not sure if the stringmatcher works on numbers though, and don't have time for a test myself now :)
function prepareInterval() {
self.callbacksData = [];
if(self.DynamicValuesList !== null) {
self.myPromise = $interval;
self.myPromise(callbackFunction, userInputInterval * 1000);
}
}
it('myUnitTest', function(){
//Prepare data
var controller = createController();
spyOn(controller, 'myPromise');
expect(controller.myPromise).toHaveBeenCalledWith(jasmine.any(Function), jasmine.stringMatching(/^[1-9][0-9]*$/));
});

Put local variable in angular.forEach loop in the global

Hello I have created a variable inside the angular.forEach loop that I need outside the loop to bind to the $scope, but i don´t know how to get outside this variable miAlumno.
Thanks.
'use strict';
angular.module('jarciaApp')
.controller('alumnoCtrl', function($routeParams, $firebaseArray) {
var ref = firebase.database().ref('PrimeroA');
var JarciaArray = $firebaseArray(ref);
var id = $routeParams.id;
var curso = $routeParams.curso;
var miAlumno;
JarciaArray.$loaded()
.then(function(miAlumno) {
angular.forEach(JarciaArray, function(alumno, miAlumno) {
if (alumno.Id == id) {
var miAlumno = alumno;
}
})
});
console.log(miAlumno);
});
Don't declare variable miAlumno in loop. Declare it outside and it will be visible.
Pay attention at that console.log(miAlumno), it probably will be always null because of the asynchronous assignement.
If I understand correctly, you are wanting to use the alumno variable you have passed into the function outside the scope of that function?
You can go about it by creating/declaring a global variable outside the function just as you did with miAlumno, then simply assign your global variable to the value your function produces. Also you have miAlumno declared twice, once outside as a global, then inside the for loop.
Apologies if I'm misunderstanding, and if so, please elaborate.
It sounds like you're using the ControllerAs syntax, which means what traditionally would be scope variables are automatically bound as properties of the controller. You can simply set the variable inside of the loop, using a cached value of this:
'use strict';
angular.module('jarciaApp')
.controller('alumnoCtrl', function($routeParams, $firebaseArray) {
var ref = firebase.database().ref('PrimeroA');
var JarciaArray = $firebaseArray(ref);
var id = $routeParams.id;
var curso = $routeParams.curso;
var me = this;
JarciaArray.$loaded()
.then(function() {
angular.forEach(JarciaArray, function(alumno) {
if (alumno.Id == id) {
me.miAlumno = alumno;
}
});
// This will work to show the value
console.log(me.miAlumno);
});
// This still won't work, since $loaded is asynchronous
console.log(this.miAlumno);
});
I've also removed the extra attempts to pass miAlumno to the then function and the callback for forEach; those are unnecessary.
Do note the comments about console.log. I believe the $loaded() is asynchronous; thus it will not be set until whatever process that is actually finishes. If you move the log to inside the then(), you'll see the result.

Accessing a JavaScript / jQuery Instance only once

I have a jQuery function which is called on several events (button click, change etc.)
This function is called in the documentReadyFunction and is feeded with start values..
everytime I call this function, parameters will be passed to the function.
My problem is: I don't want to create a new Object each time I call the function, because if I set a variable which decides if a part of the function is beeing executed or not, will be always overwritten..
What do I have to do, to access the first created instance instead of creating always a new one with every function call..
Down below is a simplyfied version of my function.. Maybe you understand my problem better then.
$.fn.doSomething = function(param1) {
var localParam = param1;
var amIcalledMoreThanOnce = parseInt(0, 10);
if (param1 == 1) {
amIcalledMoreThanOnce = amIcalledMoreThanOnce + 1;
if (amIcalledMoreThanOnce == 1) {
$('#run').val(amIcalledMoreThanOnce);
// fill form fields with URL parameters
// This shall be executed only once after getting the URL vals
} else {
// set the localParam to 0 to exit this loop and reach the outter else..
localParam = 0;
$.fn.doSomething(localParam);
}
} else {
$('#run').val(amIcalledMoreThanOnce);
// use the User Input Data not the URL Params
}
};
$.fn.doSomething(1);
$.fn.doSomething(1);
<script src="https://ajax.googleapis.com/ajax/libs/jquery/1.12.0/jquery.min.js"></script>
<input type="number" id="run">
you can use this pattern:
var nameOfYourFunction = (function() {
var initializedOnlyOnce = {};
return function() {
//use initializedOnlyOnce here.
}
})();
here what you see is; you create and run a function immediately when the code is run. The outer function immediately returns the inner function and it's assigned to nameOfYourFunction. Then you can use the nameOfYourFunction(); just as any other function. However any varible declared in the outer function will be available to the nameOfYourFunction() and initializedOnlyOnce will never be initialized again.

Updating angular.js service object without extend/copy possible?

I have 2 services and would like to update a variable in the 1st service from the 2nd service.
In a controller, I am setting a scope variable to the getter of the 1st service.
The problem is, the view attached to the controller doesn't update when the service variable changes UNLESS I use angular.extend/copy. It seems like I should just be able to set selectedBuilding below without having to use extend/copy. Am I doing something wrong, or is this how you have to do it?
controller
app.controller('SelectedBuildingCtrl', function($scope, BuildingsService) {
$scope.building = BuildingsService.getSelectedBuilding();
});
service 1
app.factory('BuildingsService', function() {
var buildingsList = [];
var selectedBuilding = {};
// buildingsList populated up here
...
var setSelectedBuilding = function(buildingId) {
angular.extend(selectedBuilding, _.find(
buildingsList, {'building_id': buildingId})
);
};
var getSelectedBuilding = function() {
return selectedBuilding;
};
...
return {
setSelectedBuilding: setSelectedBuilding,
getSelectedBuilding: getSelectedBuilding
}
});
service 2
app.factory('AnotherService', function(BuildingsService) {
...
// something happens, gives me a building id
BuildingsService.setSelectedBuilding(building_id);
...
});
Thanks in advance!
When you execute this code:
$scope.building = BuildingsService.getSelectedBuilding();
$scope.building is copied a reference to the same object in memory as your service's selectedBuilding. When you assign another object to selectedBuilding, the $scope.building still references to the old object. That's why the view is not updated and you have to use angular.copy/extend.
You could try the following solution to avoid this problem if you need to assign new objects to your selectedBuilding:
app.factory('BuildingsService', function() {
var buildingsList = [];
var building = { //create another object to **hang** the reference
selectedBuilding : {}
}
// buildingsList populated up here
...
var setSelectedBuilding = function(buildingId) {
//just assign a new object to building.selectedBuilding
};
var getSelectedBuilding = function() {
return building; //return the building instead of selectedBuilding
};
...
return {
setSelectedBuilding: setSelectedBuilding,
getSelectedBuilding: getSelectedBuilding
}
});
With this solution, you have to update your views to replace $scope.building bindings to $scope.building.selectedBuilding.
In my opinion, I will stick to angular.copy/extend to avoid this unnecessary complexity.
I dont believe you need an extend in your service. You should be able to watch the service directly and respond to the changes:
app.controller('SelectedBuildingCtrl', function($scope, BuildingsService) {
// first function is evaluated on every $digest cycle
$scope.$watch(function(scope){
return BuildingsService.getSelectedBuilding();
// second function is a callback that provides the changes
}, function(newVal, oldVal, scope) {
scope.building = newVal;
}
});
More on $watch: https://code.angularjs.org/1.2.16/docs/api/ng/type/$rootScope.Scope

Categories

Resources