Angularjs service to toggle own model object boolean using $interval - angularjs

With the help of user3696882 I managed to set up a service:
app.service('metro', function() {
this.flagValue = {value: false};
this.setFlagValue = function(flagValue){
this.flagValue.value = flagValue;
};
this.getFlagValue = function(){
return this.flagValue;
};
});
that forms the model for a boolean that is being toggled true/false every second by one of my controllers:
function GlobalCtrl( $scope , $interval , metro ) {
var th = this;
$interval(function () {
th.pulse = !th.pulse;
metro.setFlagValue( th.pulse );
}, 1000 );
}
The toggling model in my service can show up in another part of the view - accessed by a different controller:
function ViewCtrl( $scope , metro ) {
var th = this;
th.pulse = metro.getFlagValue() ;
}
Full solution available in this Plunker
Maybe I am taking things too far, but I'd like to be able to have a service depending on $interval which toggles its own model.
I made:
app.factory('loop_toggle', ['$interval', function($interval ) {
var flagObj = {value: false};
function flip() {
flagObj.value = !flagObj.value;
}
$interval(flip, 1000);
return {
current : flagObj
};
}]);
However, with my modifed controller:
function ViewCtrl( $scope , metro , loop_toggle ) {
var th = this;
th.pulse = metro.getFlagValue() ;
th.loop = loop_toggle.current.value;
}
I cannot see anything toggling: just a static "false".
Development so far available in this Plunker
I suppose I could make do with a service metronome being toggled by a controller - but isn't that having business logic in the wrong place?

You need to update th.loop after each second,
Add dependaincies into ViewCtrl
ViewCtrl.$inject = ['$scope' , 'metro' , 'loop_toggle', '$interval' ] ;
Update your th.loop using $interval
function ViewCtrl( $scope , metro , loop_toggle, $interval ) {
var th = this;
th.pulse = metro.getFlagValue() ;
$interval(function () {
th.loop = loop_toggle.current.value;
}, 1000 );
}
Updated plunker
metro service doesn't need $interval, but Why loop_toggle factory needs $interval to update value?
Here are some explanations Service vs Factory

Related

angularjs referencing service getter from controller

My Angularjs project needs a service to supply a toggling boolean to other services.
Ideally, there could be an $interval() loop inside the service (but I think it would not work like that).
I created a service with s simple setter and getter:
app.service('metro', function() {
this.setFlagValue = function(flagValue){
this.flagValue = flagValue;
}
this.getFlagValue = function(){
return this.flagValue;
}
});
A global controller toggles a boolean property in an $interval() loop
function GlobalCtrl( $scope , $interval , metro ) {
var th = this;
$interval(function () {
th.pulse = !th.pulse;
metro.setFlagValue( th.pulse );
}, 1000 );
}
As seen in this plunker the global controller's boolean is toggling, I just can't see if that controller is updating the model in the service.
I must have authored something wrong, I just can't see it.
Hope someone can help
In your ViewCtrl, When you are getting
th.pulse = metro.getFlagValue() ;
You will be getting a boolean value for first time(lets suppose it to be true) .From next time , angular will run digest cycle and check if th.pulse is changed.(It doesnt again execute function 'metro.getFlagValue()').
Since th.pulse references a value and it is not changed , It doesnt refresh in view.
Now I have changed your code to make metro.getFlagValue() return an object , when Digest cycle runs again, it checks what th.pulse has and since it contains object refrenece created in service, it goes and checks whether the object has changed.
Now the digest cycle will update as the object is indeed changed.
and view will be updated
I have included scripts which i changed to make it run
app.service('metro', function() {
this.flagValue = {value: false}
this.setFlagValue = function(flagValue){
this.flagValue.value = flagValue;
}
this.getFlagValue = function(){
return this.flagValue;
}
});
function GlobalCtrl( $scope , $interval , metro ) {
var th = this;
$interval(function () {
th.pulse = !th.pulse;
metro.setFlagValue( th.pulse );
}, 1000 );
}
function ViewCtrl( $scope , metro ) {
var th = this;
th.pulse = metro.getFlagValue() ;
}
<div ng-app="myApp" ng-controller="GlobalCtrl as gc" class="gc">
<p>Global Controller property: {{gc.pulse}}</p>
<div ng-controller="ViewCtrl as vc" class="vc">
<p>Reference to Global Controller property: {{gc.pulse}}</p>
<p>View Controller property [referencing service] : {{ vc.pulse.value }}</p>
</div>
</div>

Angular/Ionic: updating ng-change value in two controllers

I need a bit of a code review, i'm having trouble getting my ng-change function to trigger and update the value in both controllers, i've created a factory service and have injected it into both controllers but on the second AppCtrl console.log() value prints only once during initialization, and would like to have the ng-change value also update on the second controller and not only on the first.
This is what i have so far:
<ion-radio ng-repeat="rate in rates"
ng-value="rate.id"
ng-change="rateTypeChanged(rate)"
ng-checked="rate.selected"
ng-model="currentRate.selectedRate">
{{ rate.title }}
</ion-radio>
controller for sidebar:
.controller('SidebarCtrl', function($scope, typesOfRates) {
$scope.rates = typesOfRates.rateType;
$scope.currentRate = {
selectedRate: 'hourly'
};
$scope.rateTypeChanged = function(rate) {
console.log("Selected goalType, text:", rate.title, "value:", rate.id);
typesOfRates.setRate(rate.id);
}
In controller 2:
.controller('AppCtrl', function($scope, typesOfRates, $state, $rootScope) {
console.log( typesOfRates.getRate() );
//runs only once, but not again when ng-change event is triggered
my service:
.factory('typesOfRates', function typesOfRates($rootScope) {
var typesOfRates = {};
typesOfRates.myRates = [];
typesOfRates.rateType = [
{ title: "Hourly", id: "hourly", selected: true },
{ title: "Daily", id: "daily", selected: false },
{ title: "Monthly", id: "monthly", selected: false }
];
typesOfRates.currentRate = "hourly";
var setRate = function(currentRate) {
if (typesOfRates.myRates.length > 0) typesOfRates.myRates = [];
typesOfRates.myRates.push(currentRate);
}
var getRate = function() {
return typesOfRates.myRates;
}
return {
rateType: typesOfRates.rateType,
getRate: getRate,
setRate: setRate
}
});
The way you are doing to achieve the objective seems bit out of the box. The second controller will be initialized only once. If you want to access the undated value in the second controller you need to following one of the following approaches.
1) Watch for changes in typesOfRates.myRates in the second controller.
$watch is used to track changes for a model variable in the scope. The
$watch requires $scope, as we have 2 different controllers, the scopes will be different (I feel so unless you have bound the two controllers
in the same html). So it won't be the correct to use $watch in this
situation.
2) Use a broad cast receiver concept
Advantage : It's preferred as there is no continuous watching required, and triggered only when the value changes
Step 1) In the first controller, register a broadcast as:
.controller('SidebarCtrl', function($scope, typesOfRates) {
$scope.rates = typesOfRates.rateType;
$scope.currentRate = {
selectedRate: 'hourly'
};
$scope.rateTypeChanged = function(rate) {
console.log("Selected goalType, text:", rate.title, "value:", rate.id);
typesOfRates.setRate(rate.id);
//$broadcast(name, args); here name you have to give in a file
//which is commonly accessible like constants.js, just create a
//file and include in you index.html, pass your rates as args
$rootScope.$broadcast(constants_config.TYPE_RATES_CHANGED, rate.id);
}
});
Step 2) Create constants.js file and include in your index.html as:
<!-----Constants Classes---->
<script src="Constants.js"></script>
In constants.js add the following code:
var constants_config = {
TYPE_RATES_CHANGED : "TYPE_RATES_CHANGED",
}
Step 3) Register your listener in the second controller as
.controller('AppCtrl', function($scope, typesOfRates, $state, $rootScope) {
// #CallBack
// Description : Callback function for user details fetched
$scope.$on(constants_config.TYPE_RATES_CHANGED, function(args) {
//Assign the value to a global variable or a scope variable so
//that you can access it throughout your controller
$scope.Rates = typesOfRates.getRate();
//Now the console will work
console.log( typesOfRates.getRate() );
});
});
Further Reference:
- Broadcasts : $broadcast
- Listeners : $on
- Watch : $watch

angularjs singleton doesn't work

In app.js I have a variable that I use in two files/controllers:
var app = angular.module('appDemo', ['MainControllers', 'MainServices'])
.constant('myConfig', {
'backend': 'http://localhost:1236'
})
.service('mailService', function() {
var mail = {
value: 'hello world'
};
var getMail = function() {
return mail;
}
var setMail = function(email) {
mail.value = email;
}
return {
getMail: getMail,
setMail: setMail
};
);
Setting the variable from controllerOne goes fine:
angular.module('MainControllers')
.controller('MemberController', function ($scope, mainService, appDemo) {
window.onbeforeunload = function (e) {
appDemo.setMail('test#test.com');
};
But when I get the setting variable from the controllerTwo than I get the default value:
angular.module('MainControllers')
.controller('EmailController', function($scope, appDemo) {
$scope.mailAddress = appDemo.getMail();
});
Each controller is in separate file.
what is wrong?
This may be because the service itself is being reloaded because as I can see you are setting the mail in the first controller on onbeforeunload.
Services can't persist on window reloads or page refresh. They get reloaded hence reinitialized every time you reload the page.
If you want to persist the values try putting it in localStorage or sessionStorage.

angular scoped variable in controller not updating when value changed in service

I'm developing a mini-basket in angular for an ecommerce application but have a problem with a scoped variable not updating via a service.
When i click on an add to basket button in the product grid it fires the upDateMiniBasket function in the product grid controller which has the UpdateMiniBasket service injected into it.
The controller:
whiskyControllers.controller('whiskyListCtrlr', ['$scope', 'UpdateMiniBasket', '$http',
function($scope, UpdateMiniBasket, $http){
$http.get('json/whiskies.json').success(function(data){
$scope.whiskies = data;
})
$scope.updateMiniBasket = function(e){
var targetObj = e.target.getAttribute('data-item');
UpdateMiniBasket.getUpdate(targetObj);
}
}
])
Here is the service:
whiskyrumApp.factory('UpdateMiniBasket', [function(){
var miniBasketTotal = 0,
itemCount = 0;
var miniBasketItems = [{
imageUrl : '',
name : 'There are currently no items in your basket',
price: 0
}]
var getUpdate = function(obj){
miniBasketTotal = 0;
if(obj) {
if(miniBasketItems[0].price === 0) miniBasketItems.pop();
obj = jQuery.parseJSON(obj);
miniBasketItems.push(obj);
}
for(var i = 0, j = miniBasketItems.length; i < j; i++){
miniBasketTotal += parseFloat(miniBasketItems[i].price);
}
itemCount = miniBasketItems[0].price === 0 ? 0 : miniBasketItems.length;
return {
miniBasketItems : miniBasketItems,
miniBasketTotal : miniBasketTotal,
itemCount : itemCount
}
}
return {
getUpdate : getUpdate
}
}]);
The problem is that when I add a product, the function fires and calls the service ok, but a scoped variable that should update the amount of items in the basket is not updating. This variable lives in another controller for the minibasket that also has teh UpdateMiniBasket service injected.
whiskyControllers.controller('miniBasketCtrlr', ['$scope', 'UpdateMiniBasket',
function($scope, UpdateMiniBasket){
var mbItems = UpdateMiniBasket.getUpdate();
$scope.miniBasketItems = mbItems.miniBasketItems;
$scope.itemCount = mbItems.itemCount;
}])
And this is html:
<div class="mini-basket grey-box" ng-controller="miniBasketCtrlr">
<a href="#" data-toggle="dropdown" class="btn dropdown-toggle">
{{itemCount}} Items
</a>
<!-- the ng-repeat code for the mini basket which works fine -->
I just assumed that when the variables in the service are updated, that would feed through to the scoped var in the other controller as they are both linked to the same service. I thought maybe I need to add a $watch as I cant see why this {{itemCount}} is not updating.
Any help would be greatly appreciated.
If you change the return object of your UpdateMiniBasket factory to instead of returning the primitive value for the count of items but to a function like below:
return {
miniBasketItems : miniBasketItems,
miniBasketTotal : miniBasketTotal,
itemCount : function () {
return itemCount;
}
}
And then in your controller change the binding to $scope to this:
$scope.itemCount = function () {
return mbItems.itemCount();
};
You should then just have to change the binding in the html to {{ itemCount() }} and this value should then successfully update when the values in the factory update.
I have a simplified solution to this on jsbin here: http://jsbin.com/guxexowedi/2/edit?html,js,output

Testing injected service that is a constructor function

Say I have a service like this, where a car gets an engine service injected, which is a constructor function:
angular.module('car', ['engine']).factory('carCreator', function( engine ) {
var carCreator = function( settings ) {
var engineInstance = engine( settings );
engineInstance.setMiles( settings.engine.miles );
return {
brand: settings.brand;
engine: engineInstance;
}
};
return carCreator;
});
How do I test both lines in the initialization logic:
var engineInstance = engine( settings );
engineInstance.setMiles( settings.engine.miles )
1: That engine is called with settings
2: That engineInstance.setMiles is called with settings.engine.miles
This is what I'm doing right now, but with no luck:
describe('initialization', function() {
var carCreator;
var settings = {
brand: 'Ford',
engine: {
miles: 12000
}
};
var mockEngineInstance = {
setMiles: function() {}
};
window.mockEngineCreator = function() {
return mockEngineInstance;
}
beforeEach(module('car', function($provide) {
$provide.value('engine', mockEngineCreator );
}));
beforeEach(inject(function(_carCreator_) {
carCreator = _carCreator_;
}));
it('should init text object correctly on initialization', function() {
spyOn(window, 'monkEngineCreator');
spyOn(mockEngineInstance, 'setMiles');
carCreator( settings );
expect(window.mockEngineCreator).toHaveBeenCalledWith( settings );
expect(mockEngineInstance.setMiles).toHaveBeenCalledWith( settings.engine.miles );
});
});
but this test fails, saying that window.mockEngineCreator never was called. It seems that $provide creates a new copy of the function passed in, instead of keeping a reference to it. So, does anyone know how to setup a test that can test this correctly?
The reason for this is that when you run spyOn(window, 'monkEngineCreator'), the spy is put on window, not the injected value that angular uses for dependency injection.
Doing something like this should work: (untested code)
beforeEach(module('car', function($provide){
$provide.value('engine', jasmine.createSpy('engineSpy').andCallFake(function(){
return mockEngineInstance;
}));
}));
it('should init', inject(function(carCreator, engine){
var settings = {};
carCreator(settings);
expect(engine).toHaveBeenCalledWith(settings);
}));

Resources