how can I use a function which is located inside a service? - angularjs

What I am trying to do is to use function globally throughout controllers.
The problem is when I want to use the function I defined inside the service in the first function. It shows an error that it cannot find a function. I tried without this keyword but it's not working. I can go to all function when I tried in other controllers, which is a good sign that I can use this service globally.
In short, I want to use all function inside first function.
app.factory("UserService", function() {
var users = ["Peter", "Daniel", "Nina"];
return {
all: function() {
return users;
},
first: function() {
var users = this.all();
return users[0];
}
};
});
The code above was an example that I made and real code appears like this.
controller
angular.module("app").requires.push("app.region");
I put the region to app so I can use the service.
After that I made a controller like this
.controller("regionCreateController", ["$scope", "phoneMaskService", function ($scope, phoneMaskService) {
$scope.createClicked = function (data) {
data = phoneMaskService.putMaskOnRegion(data);
console.log(data);
};
}
When I put phoneMaskService which is the service I made in the app.js and it fails.
This is the error I am getting
angular.js:14110 ReferenceError: removeAllLetters is not defined
This is the actual code making errors.
.factory("phoneMaskService", [function () {
var returnMethod = {
removeAllLetters: removeAllLetters,
putMaskOn: putMaskOn,
putMaskOnRegion: putMaskOnRegion
};
return returnMethod;
function removeAllLetters(value) {
var val = value.replace(/\D+/g, '').replace('\-', '');
return val;
}
function putMaskOn(value) {
console.log(value);
value = this.removeAllLetters(value);
console.log(value);
var isMobile = parseInt(value.charAt(1)) == 2;
if (isMobile) {
var x = value.replace(/\D/g, '').substring(0, 14).match(/(\d{3})(\d{3})(\d{3,})/);
x = ' ( ' + x[1] + ' ) ' + x[2] + ' - ' + x[3];
return x;
} else {
var x = value.replace(/\D/g, '').substring(0, 14).match(/(\d{2})(\d{3})(\d{3,})/);
x = ' ( ' + x[1] + ' ) ' + x[2] + ' - ' + x[3];
return x;
}
}
function putMaskOnRegion(object) {
angular.forEach(object, function (value, key) {
if (key == "contactNumberPhone") {
var testvalue = this.removeAllLetters(value);
console.log(this);
console.log("test value" + testvalue);
object[key] = this.removeAllLetters(value);
}
});
return object;
}
}])
The error happens the line here and says removeallletters are undefined
var testvalue = this.removeAllLetters(value);

One approach to avoid binding problems is to declare the functions inside the factory:
app.factory("UserService", function() {
var users = ["Peter", "Daniel", "Nina"];
return { all: all, first: first };
function all() {
return users;
}
function first() {
var users = all();
return users[0];
}
});

I use the follwoing when declaring factories, which
creates an object within the factory declaration, binds methods to it and returns is as the the factory object.This might work in your case.
app.factory("UserService", function() {
var services = {};
services.users = ["Peter", "Daniel", "Nina"];
services.all = function() {
return services.users;
}
services.first = function() {
return services.all()[0];
}
return services;
});

Related

Adding a function to a factory (singleton) inside a controller

I have a factory called search.
I have many controllers called SearchCampaignController, SearchQuotaController and so on.
The factory search is a singleton object to which these controllers add function implementations to.
The problem is, a parent controller called SearchController must call a function inside the factory search which is not yet implemented because the child controllers execute after the parent controller executes.
It seems like I'm doing something wrong.
angular
.module('app.search')
.factory('search', search);
search.$inject = ['$http', '$window', '$state', 'CONF', '$mdToast', 'USER_ROLES', 'USER_MODULES'];
function search($http, $window, $state, CONF, $mdToast, USER_ROLES, USER_MODULES) {
var searchInfo;
function updateQueryString(params) {
$state.go(
'search',
{
searchType: params.searchType,
quotaId: params.quotaId,
campaignName: params.campaignName,
templateName: params.templateName,
passId: params.passId,
certId: params.certId
},
{notify: false}
)
}
function getQuotaById(params) {
var reqPath = CONF.apiPath + 'core/quotas/' + params.quotaId;
return $http.get(reqPath);
}
function getCampaignById(params) {
var reqPath = CONF.apiPath + 'core/quotas/' + params.quotaId + '/campaigns/' + params.campaignName;
return $http.get(reqPath);
}
function queryCampaigns(params) {
var reqPath = CONF.apiPath + 'core/quotas/' + params.quotaId + '/campaigns';
return $http.get(reqPath);
}
function getTemplateById(params) {
var reqPath = CONF.apiPath + 'core/quotas/' + params.quotaId + '/campaigns/' + params.templateName;
return $http.get(reqPath);
}
function queryTemplates(params) {
var reqPath = CONF.apiPath + 'core/campaigns/' + params.campaignName + '/templates';
return $http.get(reqPath);
}
function getPassById(params) {
var reqPath = CONF.apiPath + 'core/passes/' + params.passId;
return $http.get(reqPath);
}
function getPassbookById(params) {
var reqPath = CONF.apiPath + 'core/passbookCert/' + params.certId;
return $http.get(reqPath);
}
function queryPassbookCerts(params) {
var reqPath = CONF.apiPath + 'core/quotas/' + params.quotaId + '/passbookCerts';
return $http.get(reqPath);
}
//Global search logic
//NEED TO RE-FACTOR
function searchMasterFunction(params, obj) {
if(params.searchType){
search.changeSearchType(params.searchType);
}
updateQueryString(params);
if(params.quotaId){
search.getQuotaAndDisplayResult(params);
// search.getPassbookCertsAndDisplayResult(params);
search.updateQuotaIdInTemplateTab(params); //special - needs re-visit
}
if(params.quotaId && !params.campaignName){
search.getCampaignsAndDisplayResult(params);
}
if(params.quotaId && params.campaignName && params.templateName){
search.getCampaignAndDisplayResult(params);
search.getTemplateAndDisplayResult(params);
}else if(params.quotaId && params.campaignName){
search.getCampaignAndDisplayResult(params);
search.getTemplatesAndDisplayResult(params);
}else if(params.quotaId && params.templateName){
search.getTemplateAndDisplayResult(params);
}
if(params.campaignName){
search.getTemplatesAndDisplayResult(params);
}
if(params.passId){
search.getPassAndDisplayResult(params);
}
//getPassbookById
}
var search = {
searchInfo: searchInfo,
searchMasterFunction: searchMasterFunction,
getQuotaById: getQuotaById,
getCampaignById: getCampaignById,
queryCampaigns: queryCampaigns,
getTemplateById: getTemplateById,
queryTemplates: queryTemplates,
getPassById: getPassById,
getPassbookById: getPassbookById,
queryPassbookCerts: queryPassbookCerts
};
return search;
}
And this is my parent controller which should call the searchMasterFunction inside the factory search so that when there are values in the query string, it automatically populates any search results according to the logic inside the search factory.
angular
.module('app.search')
.controller('SearchController', SearchController);
SearchController.$inject = ['$state', 'search', '$scope', '$log'];
function SearchController($state, search, $scope, $log){
$log.warn("Executing SearchController");
var vm = this;
vm.searchType = $state.params.searchType; //re-visit
vm.tabs = ['quota', 'campaign', 'template', 'pass', 'cert'];
vm.changeSearchTypeOnTabClick = changeSearchTypeOnTabClick;
search.changeSearchType = changeSearchType;
function changeSearchTypeOnTabClick(searchType) {
$state.go('search', {searchType: searchType}, {notify: false});
}
function changeSearchType(searchType) {
vm.searchType = searchType;
}
// this function call is what is causing the problem
// search.searchMasterFunction($state.params);
}
The following is one of my child controllers which implement functions such as search.getQuotaAndDisplayResult.
angular
.module('app.search')
.controller('SearchQuotaController', SearchQuotaController);
SearchQuotaController.$inject = ['search', '$scope', '$log'];
function SearchQuotaController(search, $scope, $log){
$log.info("Executing SearchQuotaController");
var vm = this;
vm.searchInfo;
vm.quota;
vm.searchBtnClick = searchBtnClick;
search.getQuotaAndDisplayResult = getQuotaAndDisplayResult; //THIS LINE IS WHAT NEEDS ATTENTION. I'm adding a function into the `search` factory.
function searchBtnClick(params){
search.searchMasterFunction(params);
};
function getQuotaAndDisplayResult(params) {
vm.searchInfo = params; //update fields in the Quota view
search.getQuotaById(params).then(function(quota){
vm.quota = quota.data; //update the quota object in the view
});
};
}
So the problem is that SearchQuotaController runs AFTER SearchController and therefore if I try to call search.searchMasterFunction in SearchController, it will not be able to execute properly since search.searchMasterFunction relies on the child controller to execute in order for the function implementation to be complete.
Any help would be greatly appreciated. I've already considered the $broadcast method but it seems like a hack and not a real solution.
PS. The reason why I'm adding functions from child controllers into the search factory is because child controllers have access to their local $scope.

$scope.$watch does not seem to watch factory variable

I'm a beginner to angularjs. In my NFC project, I want to be able to GET from the server data based on a changing patientId.
However, I am not able to see my $watch execute correctly, even though I see that the patientId changes each time I scan a new NFC tag.
var nfc = angular.module('NfcCtrl', ['PatientRecordsService'])
nfc.controller('NfcCtrl', function($scope, NfcService, PatientRecordsService) {
$scope.tag = NfcService.tag;
$scope.patientId = NfcService.patientId
$scope.$watch(function() {
return NfcService.patientId;
}, function() {
console.log("Inside watch");
PatientRecordsService.getPatientRecords(NfcService.patientId)
.then(
function(response) {
$scope.patientRecords = response
},
function(httpError) {
throw httpError.status + " : " +
httpError.data;
});
}, true);
$scope.clear = function() {
NfcService.clearTag();
};
});
nfc.factory('NfcService', function($rootScope, $ionicPlatform, $filter) {
var tag = {};
var patientId = {};
$ionicPlatform.ready(function() {
nfc.addNdefListener(function(nfcEvent) {
console.log(JSON.stringify(nfcEvent.tag, null, 4));
$rootScope.$apply(function(){
angular.copy(nfcEvent.tag, tag);
patientId = $filter('decodePayload')(tag.ndefMessage[0]);
});
console.log("PatientId: ", patientId);
}, function() {
console.log("Listening for NDEF Tags.");
}, function(reason) {
alert("Error adding NFC Listener " + reason);
});
});
return {
tag: tag,
patientId: patientId,
clearTag: function () {
angular.copy({}, this.tag);
}
};
});
Not sure what I'm missing here - please enlighten me!
Update
Per rakslice's recommendation, I created an object to hold my data inside the factory, and now the html (with some server side delay) correctly displays the updated values when a new NFC tag is scanned.
var nfc = angular.module('NfcCtrl', ['PatientRecordsService'])
nfc.controller('NfcCtrl', function($scope, NfcService) {
$scope.tagData = NfcService.tagData;
$scope.clear = function() {
NfcService.clearTag();
};
});
nfc.factory('NfcService', function($rootScope, $ionicPlatform, $filter, PatientRecordsServi\
ce) {
var tagData = {
tag: null,
patientId: null,
patientRecords: []
};
$ionicPlatform.ready(function() {
nfc.addNdefListener(function(nfcEvent) {
//console.log(JSON.stringify(nfcEvent.tag, null, 4));
$rootScope.$apply(function() {
tagData.tag = nfcEvent.tag;
tagData.patientId = $filter('decodePayload')(tagData.tag.ndefMessage[0]);
PatientRecordsService.getPatientRecords(tagData.patientId)
.then(
function(response) {
tagData.patientRecords = response
},
function(httpError) {
throw httpError.status + " : " +
httpError.data;
});
});
console.log("Tag: ", tagData.tag);
console.log("PatientId: ", tagData.patientId);
}, function() {
console.log("Listening for NDEF Tags.");
}, function(reason) {
alert("Error adding NFC Listener " + reason);
})
});
return {
tagData: tagData,
clearTag: function() {
angular.copy({}, this.tagData);
}
};
});
Your code doesn't update the patientId value in the returned NfcService, only the local variable patientId inside the factory function.
Try saving a reference to the object you're returning in the factory function as in a local variable and use that to update the patientId.
For instance, change the creation of the object to put it in a local variable:
var nfcService = {
tag: tag,
patientId: patientId,
clearTag: function () {
angular.copy({}, this.tag);
}
};
...
return nfcService
and then change the patientId update to change the value in the object through the variable.
nfcService.patientId = $filter('decodePayload')(tag.ndefMessage[0]);
Update:
The basic fact about JavaScript that you need to understand is that when you assign one variable to another, if the first variable had a primitive data value the second variable gets a copy of that value, so changing the first variable doesn't affect the second variable after that, but if the first variable had an object reference the second variable gets pointed at that same object that the first variable is pointed at, and changing the object in the first variable after that will affect what you see through the second variable, since it's looking at the same object.
A quick experiment in the browser JavaScript console should give you the idea:
> var a = 1;
> a
1
> var b = a;
> b
1
> a = 5;
> a
5
> b
1
vs.
> var a = {foo: 1}
> var b = a
> a.foo = 5
> a.foo
5
> b.foo
5

Foreach loop in AngularJS and $http.get

I have problem with my AngularJS function. Data from first forEach is retrieved with $http.get and in second forEach, $scope.products isn't defined yet. I know that $http.get() is an asynchronous request and this is the point... But how to rewrite this function to work fine ?
$scope.getAll = function () {
var cookies = $cookies.getAll();
$scope.products = [];
var i = 0;
angular.forEach(cookies, function (v, k) {
console.log("important1:" + $scope.products);
console.log("key: " + k + ", value: " + v);
ProductsService.retrieve(k).then(function(response) {
$scope.products = $scope.products.concat(response.data);
$scope.products[i].quantity = v;
i++;
}, function (error) {
console.log('error');
});
});
console.log("important2:" + $scope.products);
angular.forEach($scope.products, function(value, key) {
$scope.total = value.quantity*value.price + $scope.total;
console.log("Quantiy: " + value.quantity);
console.log("Price: " + value.price);
});
console.log($scope.products);
console.log($scope.total);
};
I suggest that you use the $q.all().
More specifically, you would do:
$q.all([p1, p2, p3...]).then(function() {
// your code to be executed after all the promises have competed.
})
where p1, p2, ... are the promises corresponding to each of your ProductsService.retrieve(k).
Build up a list of service calls then call $q.all. Inside the then function you can put your second loop.
var listOfServiceCalls = [];
//Add to list of service calls
$q.all(listOfServiceCalls)
.then(function () {
//All calls completed THEN
angular.forEach($scope.products, function(value, key) {
$scope.total = value.quantity*value.price + $scope.total;
});
});

Restangular and UI-Bootstrap first page appears blank

I am currently getting to grips with Restangular and seem to be making some headway. I have opted to use the UI-Bootstrap for the ease of use, as I am use to working with bootstrap before.
My current issue is that I have pagination working for my controller, however the results do not appear when you first visit the page. If I visit the second page and then go back to the first page the results are there as expected. If I then choose to reload the page in anyway the results on the first page do not appear.
My code is as follows:
app.controller('BillsListCtrl', function ($scope, BillRepository) {
$scope.filteredBills = [],
$scope.currentPage = 1,
$scope.itemsPerPage = 10;
$scope.bills = BillRepository.getList();
$scope.$watch('currentPage', function() {
var begin = (($scope.currentPage - 1) * $scope.itemsPerPage),
end = begin + $scope.itemsPerPage;
$scope.filteredBills = $scope.bills.slice(begin, end);
});
console.log($scope.filteredBills);
});
The Repository:
app.factory('AbstractRepository', [
function () {
function AbstractRepository(restangular, route) {
this.restangular = restangular;
this.route = route;
}
AbstractRepository.prototype = {
getList: function (params) {
return this.restangular.all(this.route).getList(params).$object;
},
get: function (id) {
return this.restangular.one(this.route, id).get();
},
getView: function (id) {
return this.restangular.one(this.route, id).one(this.route + 'view').get();
},
update: function (updatedResource) {
return updatedResource.put().$object;
},
create: function (newResource) {
return this.restangular.all(this.route).post(newResource);
},
remove: function (object) {
return this.restangular.one(this.route, object.id).remove();
}
};
AbstractRepository.extend = function (repository) {
repository.prototype = Object.create(AbstractRepository.prototype);
repository.prototype.constructor = repository;
};
return AbstractRepository;
}
]);
Setting up the BillRepository:
app.factory('BillRepository', ['Restangular', 'AbstractRepository',
function (restangular, AbstractRepository) {
function BillRepository() {
AbstractRepository.call(this, restangular, 'bills');
}
AbstractRepository.extend(BillRepository);
return new BillRepository();
}
]);
Any light you can shed on this issue would greatly be appreciated!
If $scope.filteredBills is what's being displayed on the page, you're only populating that variable when the currentPage variable is changed. When your code runs for the first time, you set the variable and then set the watch on it, so it doesn't change and filteredBills does not get set.
Thanks goes to #ErikAGriffin for his help with this.
I made a change in my repository and removed the $object as shown below:
getList: function (params) {
return this.restangular.all(this.route).getList(params);
},
Then my controller changed to the following:
app.controller('BillsListCtrl', function ($scope, BillRepository) {
$scope.filteredBills = [],
$scope.currentPage = 1,
$scope.itemsPerPage = 10;
$scope.bills = BillRepository.getList().$object;
BillRepository.getList().then(function(data){
$scope.filteredBills = data.slice(0, $scope.itemsPerPage);
});
$scope.$watch('currentPage + itemsPerPage', function() {
var begin = (($scope.currentPage - 1) * $scope.itemsPerPage),
end = begin + $scope.itemsPerPage;
$scope.filteredBills = $scope.bills.slice(begin, end);
});
});

$watch not updating scope variable

First I want to say that I am a complete beginner in AngularJS and just attempting to understand the basic concepts. I have a background in Java and PHP.
I am building a part of a website. Right now the angular app only consists of opening and closing 2 drop down menus registrationDropDown and loginDropDown. I want them to work so that only one can be open at a time ie. if I open one, and the other is already open, the older one is forced to close.
I have a service to manage the variables that determine whether the drop downs should be open or closed and 2 controllers, one for login and one for registration, both include $watch for the respective variables.
THE PROBLEM
I want the app to work so that only one of the drop downs can be open at one time.
JSFIDDLE: http://jsfiddle.net/F5p6m/3/
angular.module("ftApp", [])
.factory('dropDownService', function () {
var loginDropDownStatus = false;
var registrationDropDownStatus = false;
return {
getLoginDropDownStatus: function () {
return loginDropDownStatus;
},
showLoginDropDown: function () {
console.log("showing login drop down");
registrationDropDownStatus = false;
loginDropDownStatus = true;
console.log("loginDropDownStatus" + loginDropDownStatus + "registrationDropDownStatus" + registrationDropDownStatus);
},
hideLoginDropDown: function () {
console.log("hiding login drop down");
loginDropDownStatus = false;
console.log("loginDropDownStatus" + loginDropDownStatus);
},
getRegistrationDropDownStatus: function () {
return registrationDropDownStatus;
},
showRegistrationDropDown: function () {
console.log("showing registration drop down");
registrationDropDownStatus = true;
loginDropDownStatus = false;
console.log("registrationDropDownStatus" + registrationDropDownStatus);
},
hideRegistrationDropDown: function () {
console.log("hiding registration drop down");
registrationDropDownStatus = false;
console.log("registrationDropDownStatus" + registrationDropDownStatus);
}
};
}) .controller("LoginDropDownController", function ($scope, dropDownService) {
$scope.loginDropDownStatus = dropDownService.getLoginDropDownStatus();
$scope.$watchCollection('loginDropDownStatus', function(newValue, oldValue) {
console.log("watcher is working");
console.log("value is " + newValue + oldValue);
console.log("LOGIN new value is " + newValue);
$scope.loginDropDownStatus = newValue;
});
$scope.toggleDropDown = function () {
if ( $scope.loginDropDownStatus == false ) {
dropDownService.showLoginDropDown();
dropDownService.hideRegistrationDropDown();
$scope.loginDropDownStatus = true;
} else if ( $scope.loginDropDownStatus == true ) {
dropDownService.hideLoginDropDown();
$scope.loginDropDownStatus = false;
}
};
})
.controller("RegistrationDropDownController", function ($scope, dropDownService) {
$scope.registrationDropDownStatus = dropDownService.getRegistrationDropDownStatus();
$scope.$watch('registrationDropDownStatus', function(newValue, oldValue) {
console.log("watcher is working");
console.log("value is " + newValue + oldValue);
console.log("new value is " + newValue);
$scope.registrationDropDownStatus = newValue;
});
$scope.toggleDropDown = function () {
if ( $scope.registrationDropDownStatus == false ) {
dropDownService.showRegistrationDropDown();
dropDownService.hideLoginDropDown();
$scope.registrationDropDownStatus = true;
} else if ( $scope.registrationDropDownStatus == true ) {
dropDownService.hideRegistrationDropDown();
$scope.registrationDropDownStatus = false;
}
};
})
Edit:
Here is probably the shortest option:
angular.module("ftApp", [])
.controller("ctrl", function ($scope) {
$scope.toggle = function(menu){
$scope.active = $scope.active === menu ? null : menu;
}
})
FIDDLE
One controller, no service.
Previous Answer:
I think you have quite a bit of code to get something very simple done. Here is my solution:
angular.module("ftApp", [])
.service('dropDownService', function () {
this.active = null;
this.toggle = function(menu){
this.active = this.active === menu ? null : menu;
}
})
.controller("LoginDropDownController", function ($scope, dropDownService) {
$scope.status = dropDownService;
$scope.toggleDropDown = function () {
dropDownService.toggle("login");
};
})
.controller("RegistrationDropDownController", function ($scope, dropDownService) {
$scope.status = dropDownService;
$scope.toggleDropDown = function () {
dropDownService.toggle("reg");
};
})
FIDDLE
You can make it even shorter by only using one controller. You wouldn't even need the service then.
You are overcomplicating things. All you need your service to hold is a property indicating which dorpdown should be active.
Then you can change that property's value from the controller and check the value in the view to determine if a dropdown should be shown or hidden.
Something like this:
<!-- In the VIEW -->
<li ng-controller="XyzController">
<a ng-click="toggleDropdown()">Xyz</a>
<div ng-show="isActive()">Dropdown</div>
</li>
/* In the SERVICE */
.factory('DropdownService', function () {
return {
activeDropDown: null
};
})
/* In the CONTROLLER */
controller("XyzDropdownController", function ($scope, DropdownService) {
var dropdownName = 'xyz';
var dds = DropdownService;
$scope.isActive = function () {
return dropdownName === dds.activeDropdown;
};
$scope.toggleDropdown = function () {
dds.activeDropdown = (dds.activeDropdown === dropdownName) ?
null :
dropdownName;
};
})
See, also, this short demo.
Based on what exactly you are doing, there might be other approaches possible/preferrable:
E.g. you could use just on controller to control all dropdowns
or you could use two instances of the same controller to control each dropdown.
See my updated fiddle. I simplified the code and removed the service. Because you just used two variables to control visibility, you don't need a service nor $watch. You need to keep variables in the $rootScope, otherwise changes in a controller is not visible to another controller due to isolated scopes.
angular.module("ftApp", [])
.controller("LoginDropDownController", function ($scope, $rootScope) {
$rootScope.loginDropDownStatus = false;
$scope.toggleDropDown = function () {
if ($rootScope.loginDropDownStatus == false) {
$rootScope.registrationDropDownStatus = false;
$rootScope.loginDropDownStatus = true;
} else if ($rootScope.loginDropDownStatus == true) {
$rootScope.loginDropDownStatus = false;
}
};
}).controller("RegistrationDropDownController", function ($scope, $rootScope) {
$rootScope.registrationDropDownStatus = false;
$scope.toggleDropDown = function () {
if ($rootScope.registrationDropDownStatus === false) {
$rootScope.loginDropDownStatus = false;
$rootScope.registrationDropDownStatus = true;
} else if ($scope.registrationDropDownStatus === true) {
$rootScope.registrationDropDownStatus = false;
}
};
})
This code can be simplified further. I'll leave that to you.

Resources