Angularjs binding value from async service - angularjs

Having resolved a couple of errors thanks to #sehaxx in Angularjs binding value from service I would like introduce async in the example as in the following code where a variable is initialized asynchronously and it's value is not reflected in the view.
(function(angular) {
'use strict';
angular.
module('myServiceModule', []).
controller('MyController', ['$scope', 'notify','$log', function($scope, notify, $log) {
this.clickCount = 0;
this.clickLimit = notify.clickLimit();
this.callNotify = function(msg) {
notify.push(msg);
this.clickCount = notify.clickCount();
$log.debug("[controller] Click count is now", this.clickCount, " and limit is ", this.clickLimit);
};
}]).
factory('notify', ['$window','$log', '$timeout', function(win,$log, $timeout) {
var msgs = [];
var clickCounter = 0;
var countLimit = 0;
$timeout( function(){
countLimit = Math.floor(Math.random() * 10)+1;
$log.debug("[service] Click limit initialized at", countLimit);
return countLimit;
}, 10);
return {
clickLimit: function(){
return countLimit;
},
clickCount: function() {
clickCounter = msgs.length;
$log.debug("[service] You are clicking, click count is now", clickCounter, " limit is ", countLimit);
return clickCounter;
},
push: function(msg) {
msgs.push(msg);
clickCounter = msgs.length;
$log.debug("[service] Counter is", clickCounter, " on ", countLimit);
if (msgs.length === countLimit) {
win.alert(msgs.join('\n'));
msgs = [];
}
}
}
}]);
})(window.angular);
Working example in pen

The reason this isn't working as expected is due to the fact that countLimit is a Primitive, and Primitives are always passed byVal rather than byRef, so there is no way for the factory to update the value at a later date.
Changing the countLimit to an Object fixes this, because the value of the Object is the Reference to the properties of the Object. In other words, we are able to pass byRef. We just have to update our code to refer to the Object's child property instead of referring to the value directly, i.e. countLimit.value.
working example: https://codepen.io/anon/pen/VVmdbE?editors=1111
(function(angular) {
'use strict';
angular.
module('myServiceModule', []).
controller('MyController', ['$scope', 'notify', '$log', function($scope, notify, $log) {
this.clickCount = 0;
this.clickLimit = notify.clickLimit();
this.callNotify = function(msg) {
notify.push(msg);
this.clickCount = notify.clickCount();
$log.debug("[controller] Click count is now", this.clickCount, " and limit is ", this.clickLimit.value);
};
}]).
factory('notify', ['$window', '$log', '$timeout', function(win, $log, $timeout) {
var msgs = [];
var clickCounter = 0;
var countLimit = {
value: 0,
};
$timeout(function() {
countLimit.value = Math.floor(Math.random() * 10) + 1;
$log.debug("[service] Click limit initialized at", countLimit.value);
return countLimit;
}, 10);
return {
clickLimit: function() {
$log.debug("called clickLimit");
return countLimit;
},
clickCount: function() {
clickCounter = msgs.length;
$log.debug("[service] You are clicking, click count is now", clickCounter, " limit is ", countLimit);
return clickCounter;
},
push: function(msg) {
msgs.push(msg);
clickCounter = msgs.length;
$log.debug("[service] Counter is", clickCounter, " on ", countLimit);
if (msgs.length === countLimit.value) {
win.alert(msgs.join('\n'));
msgs = [];
}
}
}
}]);
})(window.angular);
<!doctype html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title>Example - example-services-usage-production</title>
<script src="https://code.angularjs.org/snapshot/angular.min.js"></script>
<script src="script.js"></script>
</head>
<body ng-app="myServiceModule">
<div id="simple" ng-controller="MyController as self">
<p>Let's try this simple notify service, injected into the controller...</p>
<input ng-init="message='test'" ng-model="message">
<button ng-click="self.callNotify(message);">NOTIFY</button>
<p>(you have to click {{self.clickLimit.value-self.clickCount}} times more to see an alert)</p>
<div>You have clicked {{ self.clickCount }} times</div>
</div>
</body>
</html>

Related

Angular $watch function after change variable

I´m trying to build a delayed message box. I observe that apply is called when the app is started, as describe in the API docs. But when the observed value is changed, it isn´t called. The MessageCtrl is inner controller. Why isn´t watch called after changing message var?
angular.module('myApp',[]).controller('MessageCtrl', function($scope) {
$scope.getMessage = function() {
setTimeout(function() {
$scope.$parent.message = {text : ""};
$scope.$apply(function() {
console.log('message:' + $scope.$parent.message.text);
});
}, 2000);
}
$scope.getMessage();
})
.controller('MainCtrl',function($scope){
$scope.message={text:"oi"};
$scope.$watch("message", function(newValue, oldValue){
console.log("watch " + $scope.message.text);
});
});
The inner controller MessageCtrl will get the text message and show it for 2 seconds.
<body ng-app="myApp" ng-controller="MainCtrl">
<div ng-controller="MessageCtrl">
Message:{{message.text}}
</div>
</body>
As far as I can tell your code does work. You are however using $apply incorrectly. $apply lets you inform angular that you are changing state outside of the usual methods and that is should thus re-evaluate bindings etc. So you should be using
$scope.$apply(function() { $scope.$parent.message = {text: 'new message'}; });
angular.module('myApp', []).controller('MessageCtrl', function($scope) {
$scope.getMessage = function() {
setTimeout(function() {
$scope.$apply(function() {
$scope.$parent.message = {
text: "new message"
};
console.log('message:' + $scope.$parent.message.text);
});
}, 2000);
}
$scope.getMessage();
})
.controller('MainCtrl', function($scope) {
$scope.message = {
text: "oi"
};
$scope.$watch("message", function(newValue, oldValue) {
console.log("watch " + $scope.message.text);
});
});
<script src="https://ajax.googleapis.com/ajax/libs/angularjs/1.2.23/angular.min.js"></script>
<div ng-app="myApp" ng-controller="MainCtrl">
<div ng-controller="MessageCtrl">
Message:{{message.text}}
</div>
</div>
One thing of note, you should really use the angular $timeout service which allows you to set timeouts in your app, but you don't have to handle calling $apply.
angular.module('myApp', []).controller('MessageCtrl', function($scope, $timeout) {
$scope.getMessage = function() {
$timeout(function() {
$scope.$parent.message = {
text: "new message"
};
console.log('message:' + $scope.$parent.message.text);
}, 2000);
}
$scope.getMessage();
})
.controller('MainCtrl', function($scope) {
$scope.message = {
text: "oi"
};
$scope.$watch("message", function(newValue, oldValue) {
console.log("watch " + $scope.message.text);
});
});
<script src="https://ajax.googleapis.com/ajax/libs/angularjs/1.2.23/angular.min.js"></script>
<div ng-app="myApp" ng-controller="MainCtrl">
<div ng-controller="MessageCtrl">
Message:{{message.text}}
</div>
</div>
You need to use $scope.apply method because setTimeout function is not angular native function :
setTimeout(function() {
$scope.apply({
$scope.$parent.message = {text : ""}; $scope.$apply(function() { console.log('message:' + $scope.$parent.message.text); });}) ;}, 2000);
Alternatively you can also use $timeout inbuilt-service in angular like this :
angular.module('myApp',[]).controller('MessageCtrl', function($scope, , $timeout) { $scope.getMessage = function() { $, $timeout(function() { $scope.$parent.message = {text : ""}; $scope.$apply(function() { console.log('message:' + $scope.$parent.message.text); }); }, 2000); } $scope.getMessage(); })
use $timeout instead $apply that executes $apply implicity
angular.module('myApp',[]).controller('MessageCtrl', function($scope, $timeout) {
$scope.getMessage = function() {
$timeout(function() {
$scope.$parent.message = {text : ""};
$scope.$apply(function() {
console.log('message:' + $scope.$parent.message.text);
});
}, 2000);
}
$scope.getMessage();
})

Angular skip the get method calling web service

var l = $scope.PendingKOTitems.length,
k = 0;
for (k = 0; k < l; k = k + 1) {
sItemCode = $scope.PendingKOTitems[k]["sItemCode"]
sOutletCode = $scope.selectedOutlet.sOutletCode;
sFinal = sOutletCode + "/" + sSubCatgCode
$http.get(sServiceURL + 'ItemMst/' + sFinal).then(function (resp) {
$scope.Items = resp.data;
iindexItem = getIndexOf($scope.Items, sItemCode, "sItemCode");
}, function (err) {
console.error('ERR', err);
// err.status will contain the status code
})
sCheckValue = $scope.CheckboxSelection.value;
$scope.invoice.items.push({
KOTNo: $scope.KOTNumValue,
ActiveMode: $scope.lActiveMode,
SubCatgCode: sSubCatgCode,
SubCatgDesc: sSubCatgDesc,
indexSubCatg: iindexSubCatg,
ItemCode: sItemCode,
ItemDesc: sItemDesc,
indexItem: iindexItem,
Qty: sQty,
OutletCode: sOutletCode,
BillType: sBillType,
MemberCode: sMemberCode,
RoomNum: sRoomNum,
BookingCode: dBookingCode,
TableCode: sTableCode,
WaiterCode: sWaiterCode,
UserID: sUserID
});
}
The get methode not fire in the for loop, after perform for loop it will fire.
I want get 'iindexItem' value for load select based on item sub category. But get method fire after the 'push' operation perform. so i was not able to get 'iindexItem' value. this my problem.
I tried this and this seems to work fine for me --
<!DOCTYPE html>
<html>
<head>
<title>Page Title</title>
<script src="https://cdnjs.cloudflare.com/ajax/libs/angular.js/1.4.1/angular.js"></script>
</head>
<body ng-app="app">
<div ng-controller="ctrl">
<h1>This is a Heading</h1>
<p>This is a paragraph.</p>
</div>
<script type="text/javascript">
var angularapp = angular.module("app",[]);
angularapp.controller('ctrl', ['$scope', '$window', '$http', '$filter', '$timeout', MyCtrl]);
function MyCtrl($scope, $window, $http, $filter, $timeout){
for(var i=0; i<3; i++){
$http.get("http://httpbin.org/get").then(function (resp) {
debugger;
$scope.Items = resp.data; // here I set my breakpoint
console.log(i);
console.log(resp.data);
}, function (err) {
console.error('ERR', err);
// err.status will contain the status code
});
}
}
</script>
</body>
</html>
The debugger is hit on every iteration in the for loop.

Dynamic default values for input box in angularJs

I have a value which i get from a controller and two input box.
I need that whenever i enter any value in one input box. difference of the value retrieved from controller and input box get displayed in the other input box using angularJs.
e.g:-
{{sum}} --> is the value which I get from controller.
<input type="number" ng-model="firstnumber" />
<input type="number" ng-model="secondnumber"/>
What I tried was making service for setting and getting the sum value and watch to change the value every time a value is changed.
My service is :-
angular.module('myapp').service("cmModalService", function($scope){
var sum= ={};
getSum = function(){
return sum;
}
setSum = function(value){
sum=value;
};
});
In a controller I have defined
$scope.$watch('firstnumber', function(newValue, oldValue) {
var sum=cmModalService.getSum();
if(newValue!=null)
{ secondnumber = sum -firstnumber;
}
});
$scope.$watch('secondnumber', function(newValue, oldValue) {
var sum=cmModalService.getSum();
if(newValue!=null)
{ firstnumber = sum -secondnumber;
}
});
But whenever i change values in input box the flow never goes to watch method, and the values doesn't change.
Is there any other method also to achieve this.?
I have tried using ng-change also but still not able to get the exact result.
And inside controller i have defined the change methods as
$scope.changefirstnumber=function(firstnumber, sum){
$scope.secondnumber = sum- firstnumber;
};
$scope.changesecondnumber=function(secondnumber, sum){
$scope.firstnumber= sum- secondnumber;
};
and in html
[Plunker link]
You are not setting the scoped variable. Try this.
$scope.$watch('firstnumber', function(newValue, oldValue) {
var sum=cmModalService.getSum();
if(newValue!=null)
{ $scope.secondnumber = sum -$scope.firstnumber;
}
});
$scope.$watch('secondnumber', function(newValue, oldValue) {
var sum=cmModalService.getSum();
if(newValue!=null)
{ $scope.firstnumber = sum - $scope.secondnumber;
}
});
Working Plunkr
EDIT
With some new information you provided, is this what you're after? http://jsfiddle.net/37gv1kbe/
var app = angular.module('myApp',[]);
app.controller('MyController', function($scope,cmModalService)
{
$scope.firstnumber = 5;
$scope.secondnumber = 10;
$scope.$watch('firstnumber', function()
{
$scope.total = cmModalService.getSum() - $scope.firstnumber
});
$scope.$watch('secondnumber', function()
{
$scope.total = cmModalService.getSum() - $scope.secondnumber;
});
});
app.controller('MySecondController', function($scope,cmModalService)
{
$scope.rand = Math.round(Math.random() * 100);
cmModalService.setSum($scope.rand);
});
app.service('cmModalService', function()
{
var sum;
return {
getSum: function()
{
return sum;
},
setSum: function(value)
{
sum = value;
}
}
});
ORIGINAL ANSWER
regarding my comment, if you need to access the total in your controller, you can just save the val of firstnumber and secondnumber like so
http://jsfiddle.net/pvqm4tcw/
var app = angular.module('myApp',[]);
app.controller('MyController', function($scope)
{
$scope.firstnumber = 5;
$scope.secondnumber = 10;
$scope.$watch('firstnumber', function()
{
$scope.total = $scope.firstnumber + $scope.secondnumber;
});
$scope.$watch('secondnumber', function()
{
$scope.total = $scope.firstnumber + $scope.secondnumber;
});
});
html:
<body ng-app="myApp">
<div ng-controller="MyController">
<input type="number" ng-model="firstnumber" />
<br>
<input type="number" ng-model="secondnumber"/>
<br>
{{total}}
</div>
</body>
If you're using Angular 1.3+ they have a $watchGroup which can make the code even smaller
var app = angular.module('myApp',[]);
app.controller('MyController', function($scope)
{
$scope.firstnumber = 5;
$scope.secondnumber = 10;
$scope.$watchGroup(['firstnumber','secondnumber'], function()
{
$scope.total = $scope.firstnumber + $scope.secondnumber;
});
});

AngularJS Chart Directive - Data loaded in async service not updating chart

I am having one chart directive created, and I am bootstrpping the app after loading google api. In following code, a simple data table is working fine. But when I load data from server in async manner, chart is not being displayed.
Controller
'use strict';
myNetaInfoApp.controller('allCandidatesController', [
'$scope','allCandidates2009Svc', '$timeout',
function ($scope, allCandidates2009Svc, $timeout) {
$scope.data1 = {};
$scope.data1.dataTable = new google.visualization.DataTable();
$scope.data1.dataTable.addColumn("string", "Party");
$scope.data1.dataTable.addColumn("number", "qty");
$scope.data1.dataTable.title = "ASDF";
$timeout( function (oldval, newval) {
allCandidates2009Svc.GetPartyCriminalCount().then(function(netasParty) {
var i = 0;
for (var key in netasParty) {
$scope.data1.dataTable.addRow([key.toString(), netasParty[key]]);
i++;
if (i > 20) break;
}
});
});
$scope.dataAll = $scope.data1;
//sample data
$scope.data2 = {};
$scope.data2.dataTable = new google.visualization.DataTable();
$scope.data2.dataTable.addColumn("string", "Name");
$scope.data2.dataTable.addColumn("number", "Qty");
$scope.data2.dataTable.addRow(["Test", 1]);
$scope.data2.dataTable.addRow(["Test2", 2]);
$scope.data2.dataTable.addRow(["Test3", 3]);
}
]);
Service
'use strict';
myNetaInfoApp.factory('allCandidates2009Svc', ['$http', '$q',
function ($http, $q) {
var netas;
return {
GetPartyCriminalCount: function () {
var deferred = $q.defer();
$http.get('../../data/AllCandidates2009.json')
.then(function (res) {
netas = res;
if (netas) {
var finalObj = {};
_.each(netas.data, function(neta) {
finalObj[neta.pty] = finalObj[neta.pty] ? finalObj[neta.pty] + 1 : 1;
});
deferred.resolve(finalObj);
}
});
return deferred.promise;
}
};
}]);
Directive
"use strict";
var googleChart = googleChart || angular.module("googleChart", []);
googleChart.directive("googleChart", function () {
return {
restrict: "A",
link: function ($scope, $elem, $attr) {
var dt = $scope[$attr.ngModel].dataTable;
var options = {};
if ($scope[$attr.ngModel].title)
options.title = $scope[$attr.ngModel].title;
var googleChart = new google.visualization[$attr.googleChart]($elem[0]);
$scope.$watch($attr.ngModel, function (oldval, newval) {
googleChart.draw(dt, options);
});
}
};
});
HTML
<div ng-controller="allCandidatesController">
<div class="col-lg-6">
<h2>Parties and Candidates with Criminal Charges</h2>
<div google-chart="PieChart" ng-model="dataAll" class="bigGraph"></div>
<!--<p><a class="btn btn-primary" href="#" role="button">View details »</a></p>-->
</div>
<div class="col-lg-6">
<h2>Heading</h2>
<div google-chart="BarChart" ng-model="data2" class="bigGraph"></div>
</div>
</div>
I think you need to wrap your function body in allCandidates2009Svc factory with scope.$apply(). But the return deferred.resolve() will be outside scope.$apply().
function asyncGreet(name) {
var deferred = $q.defer();
setTimeout(function() {
// since this fn executes async in a future turn of the event loop, we need to wrap
// our code into an $apply call so that the model changes are properly observed.
scope.$apply(function() {
deferred.notify('About to greet ' + name + '.');
if (okToGreet(name)) {
deferred.resolve('Hello, ' + name + '!');
} else {
deferred.reject('Greeting ' + name + ' is not allowed.');
}
});
}, 1000);
return deferred.promise;
}
Read the docs here
http://docs.angularjs.org/api/ng.$q

Forcing a ng-src reload

How can I force angularjs to reload an image with an ng-src attribute, when the url of the image has not changed, but its contents has?
<div ng-controller='ctrl'>
<img ng-src="{{urlprofilephoto}}">
</div>
An uploadReplace service that performs a file upload, is replacing the content of the image, but not the url.
app.factory('R4aFact', ['$http', '$q', '$route', '$window', '$rootScope',
function($http, $q, $route, $window, $rootScope) {
return {
uploadReplace: function(imgfile, profileid) {
var xhr = new XMLHttpRequest(),
fd = new FormData(),
d = $q.defer();
fd.append('profileid', profileid);
fd.append('filedata', imgfile);
xhr.onload = function(ev) {
var data = JSON.parse(this.responseText);
$rootScope.$apply(function(){
if (data.status == 'OK') {
d.resolve(data);
} else {
d.reject(data);
}
});
}
xhr.open('post', '/profile/replacePhoto', true)
xhr.send(fd)
return d.promise;
}
}
}]);
When the uploadReplace returns, I don't know how I can force the image to reload
app.controller('ctrl', ['$scope', 'R4aFact', function($scope, R4aFact){
$scope.clickReplace = function() {
R4aFact.uploadReplace($scope.imgfile, $scope.pid).then(function(){
// ?? here I need to force to reload the imgsrc
})
}
}])
An easy workaround is to append a unique timestamp to ng-src to force image reload as follows:
$scope.$apply(function () {
$scope.imageUrl = $scope.imageUrl + '?' + new Date().getTime();
});
or
angular.module('ngSrcDemo', [])
.controller('AppCtrl', ['$scope', function ($scope) {
$scope.app = {
imageUrl: "http://example.com/img.png"
};
var random = (new Date()).toString();
$scope.imageSource = $scope.app.imageUrl + "?cb=" + random;
}]);
Perhaps it could be as simple as adding a decache query string to the image URL? ie.
var imageUrl = 'http://i.imgur.com/SVFyXFX.jpg';
$scope.decachedImageUrl = imageUrl + '?decache=' + Math.random();
This should force it to reload.
An "angular approach" could be creating your own filter to add a random querystring parameter to the image URL.
Something like this:
.filter("randomSrc", function () {
return function (input) {
if (input) {
var sep = input.indexOf("?") != -1 ? "&" : "?";
return input + sep + "r=" + Math.round(Math.random() * 999999);
}
}
})
Then you can use it like this:
<img ng-src="{{yourImageUrl | randomSrc}}" />
Try This
app.controller('ctrl', ['$scope', 'R4aFact', function($scope, R4aFact){
$scope.clickReplace = function() {
R4aFact.uploadReplace($scope.imgfile, $scope.pid).then(function(response){
$scope.urlprofilephoto = response + "?" + new Date().getTime(); //here response is ur image name with path.
});
}
}])
I resorted to make a directive to put random param in the src, but only if the image changes, so I don't mess that much with the caching.
I use it to update the user's profile pic in the navbar when they update it via AJAX, which doesn't happen that often.
(function() {
"use strict";
angular
.module("exampleApp", [])
.directive("eaImgSrc", directiveConstructor);
function directiveConstructor() {
return { link: link };
function link(scope, element, attrs) {
scope.$watch(attrs.eaImgSrc, function(currentSrc, oldSrc) {
if (currentSrc) {
// check currentSrc is not a data url,
// since you can't append a param to that
if (oldSrc && !currentSrc.match(/^data/)) {
setSrc(currentSrc + "?=" + new Date().getTime());
} else {
setSrc(currentSrc);
}
} else {
setSrc(null);
}
})
function setSrc(src) { element[0].src = src; }
}
}
})();
<script src="https://ajax.googleapis.com/ajax/libs/angularjs/1.2.23/angular.min.js"></script>
<div ng-app="exampleApp">
<div>
<img ea-img-src="src"></img>
</div>
<button ng-click="src = 'http://placehold.it/100x100/FF0000'">IMG 1</button>
<button ng-click="src = 'http://placehold.it/100x100/0000FF'">IMG 2</button>
<button ng-click="src = 'http://placehold.it/100x100/00FF00'">IMG 3</button>
</div>

Resources