Service data binding, trouble to get $apply within $watch to run - angularjs

I am trying to set up a very simple AngularJS app, but I have trouble getting my $watch and $apply to run.
I just (for now) want to display the length of an Array which I receive in a JSON object. I set up a watcher to bind my Service-data to view and put an $apply call into it, but somehow this doesn't work.
When removing the $apply() my view is updating, but it's updating the previous value (hence I suppose everything else works as supposed).
HTML:
<div ng-controller="scandataCtrl">
{{scandata.length}}
</div>
Javascript:
app.controller('scandataCtrl',['$scope', 'DataHandler', function($scope, DataHandler) {
$scope.scandata = DataHandler.getScanData();
$scope.$watch(
function(){
return DataHandler.getScanData();
},
function(newVal, oldVal){
$scope.$apply(function(){
$scope.scandata = DataHandler.getScanData();
});
});
}]);
app.service('DataHandler', function()
{
var scanresult = [];
this.getScanData = function()
{
return scanresult;
};
this.msgreceive = function(msg)
{
var msgobj = JSON.parse(msg);
switch (msgobj.MessageType)
{
case 'SCANRESULT':
scanresult = msgobj.MessageData.myArray;
break;
default:
alert("error: undefined message received")
break;
}
};
});
I googled a lot but coudn't figure it out.

You should use $timeout instead of $apply:
$scope.$watch(function(){
return DataHandler.getScanData();
}, function(newVal, oldVal){
$timeout(function(){
$scope.scandata = DataHandler.getScanData();
});
});
Timeout will internally call $apply but in a safe manner which will not throw an exception of digest is already in progress.

Related

Angular two way binding not working

I'm very new to angularjs and I want to establish a connection to my server and dynamically show the result to user. so far I've tried:
angular.module('myApp.controllers', []).controller('socketsController', function($scope) {
$scope.socket = {
client: null,
stomp: null
};
$scope.reconnect = function() {
setTimeout($scope.initSockets, 10000);
};
$scope.notify = function(message) {
$scope.result = message.body;
};
$scope.initSockets = function() {
$scope.socket.client = new SockJS('/resources');
$scope.socket.stomp = Stomp.over($scope.socket.client);
$scope.socket.stomp.connect({}, function() {
$scope.socket.stomp.subscribe('/user/topic/messages', $scope.notify);
});
$scope.socket.client.onclose = $scope.reconnect;
};
$scope.initSockets();
});
But when I use {{result}} nothing appears.
UPDATE
The server response is totally right with console.log(message.body).
I guess, the callback is not taking the scope properly. Try call $scope.$apply(); after you attach the message.body to result :
$scope.notify = function(message) {
$scope.result = message.body;
$scope.$apply();
};
$scope.$apply() triggers an angular digest cycle whcih will update all the bindings..
Call it inside a timeout function but inject $timeout first it will call the digest cycle and update the value.
$timeout(function(){
$scope.result = message.body;});

ng-show do not change when i fire function

Can someone explain why spinerChange() function do not work properly? http://jsfiddle.net/HB7LU/9907/
<div ng-controller="naujienosControler">
<button type="button" ng-click="spinerButtonChange()">Click Me!</button>
<div class="spinner" ng-show="spiner" >
<div class="cube1"></div>
<div class="cube2"></div>
</div>
</div>
var myApp = angular.module('myApp',[]);
myApp.controller('naujienosControler', function ($scope) {
var status = true;
$scope.spiner = status;
$scope.spinerButtonChange = function(){$scope.spiner = !$scope.spiner;};
function spinerChange(){
setTimeout(function(){ alert("Why spiner dont disapear?????????"); $scope.spiner = false;}, 3000);
console.log($scope.spiner);
};
spinerChange();
});
Inject and use $timeout since you want angular to perform a digest after you do you action.
myApp.controller('naujienosControler', function ($scope, $timeout){
$timeout(function(){ $scope.spiner = false; }, 3000});
}
Edit (Thanks lechariotdor) : It's always a good practice to use "the angularjs world" wrappers since they run the $apply method that performs a digest on your scope and "syncs" the model with the change that occured.
because javascript setTimeout is a event which is not trigger in angularjs scope, so angular doesn't know about changes outside the scope.
there is a way to achieve is use $timeout instead of setTimeout as below, here is the DOC for $timeout
$timeout(function() {
$scope.spiner = !$scope.spiner;
}, 3000)
and don't forget to inject $timeout in to the controller as,
myApp.controller('naujienosControler', function ($scope, $timeout) {....
here is the update
there is another alternative using $scope.$appy() here is a good tutorial about $apply()
function spinerChange(){
setTimeout(function(){
$scope.spiner = !$scope.spiner;
$scope.$apply();
}, 3000);
};
spinerChange();
});
OR
function spinerChange(){
setTimeout(function(){
$scope.$apply(function() {
$scope.spiner = !$scope.spiner;
});
}, 3000);
};
spinerChange();
});
Use :-
$timeout(function(){ alert("Why spiner dont disapear?????????"); $scope.spiner = false;}, 500);

Angularjs situations where something doesn't work without timeout

I've come to few situations in angularjs where only timeout solves the problem. I'd really like to understand why this happens and how to solve this.
Examples:
opLibrary.directive('opClick', function($parse, $location, $timeout, opDebug) {
return function(scope, element, attrs) {
var action = attrs.opClick.substring(0, 1) == '/' ? attrs.opClick : $parse(attrs.opClick);
var event = opDebug.desktop ? 'mousedown' : 'touchstart';
element.bind(event, function(e) {
e.preventDefault();
$timeout(function() {
if (angular.isFunction(action)) action(scope);
else $location.path(action);
}, 0);
});
}
});
without timeout $location.path just doesn't trigger
$.getScript('//connect.facebook.net/en_UK/all.js', function(){
FB.init({
appId: 'xxx',
});
$timeout(function() {
$scope.fbInitComplete = true;
}, 0);
});
without timeout view is not updated based on fbInitComplete change, though it updates just before view change as if value of the variable did change, but scope did not catch it
Use $scope.apply instead of timeout:
scope.$apply(function() {
if (angular.isFunction(action)) action(scope);
else $location.path(action);
});
Reason: You must notify angular when something is done asynchronously, for example when performing ajax call ($http does $scope.$apply for you).

AngularJS : Basic $watch not working

I'm attempting to set up a watch in AngularJS and I'm clearly doing something wrong, but I can't quite figure it out. The watch is firing on the immediate page load, but when I change the watched value it's not firing. For the record, I've also set up the watch on an anonymous function to return the watched variable, but I have the exact same results.
I've rigged up a minimal example below, doing everything in the controller. If it makes a difference, my actual code is hooked up in directives, but both are failing in the same way. I feel like there's got to be something basic I'm missing, but I just don't see it.
HTML:
<div ng-app="testApp">
<div ng-controller="testCtrl">
</div>
</div>
JS:
var app = angular.module('testApp', []);
function testCtrl($scope) {
$scope.hello = 0;
var t = setTimeout( function() {
$scope.hello++;
console.log($scope.hello);
}, 5000);
$scope.$watch('hello', function() { console.log('watch!'); });
}
The timeout works, hello increments, but the watch doesn't fire.
Demo at http://jsfiddle.net/pvYSu/
It's because you update the value without Angular knowing.
You should use the $timeout service instead of setTimeout, and you won't need to worry about that problem.
function testCtrl($scope, $timeout) {
$scope.hello = 0;
var t = $timeout( function() {
$scope.hello++;
console.log($scope.hello);
}, 5000);
$scope.$watch('hello', function() { console.log('watch!'); });
}
Or you could call $scope.$apply(); to force angular to recheck the values and call watches if necessary.
var t = setTimeout( function() {
$scope.hello++;
console.log($scope.hello);
$scope.$apply();
}, 5000);
You can use without $interval and $timeout
$scope.$watch(function() {
return variableToWatch;
}, function(newVal, oldVal) {
if (newVal !== oldVal) {
//custom logic goes here......
}
}, true);
It can also happen because the div is not registered with the controller. Add a controller to your div as follows and your watch should work:
<div ng-controller="myController">

AngularJS bind value into data attribute

Does anyone know how to bind an interpolated value into a data attribute using AngularJS?
<input type="text" data-custom-id="{{ record.id }}" />
Angular doesn't seem to interpolate that value since its apart of the structure of the element. Any ideas how to fix this?
Looks like there isn't a problem after all. The template is parsed and my controller was downloading the data, but when the template was being parsed data wasn't there yet. And the directive I put needs the data to be there os in the mean time its just picking up empty macro data.
The way that I solved this was with the $watch command:
$scope.$watch('ready', function() {
if($scope.ready == true) {
//now the data-id attribute works
}
});
Then when the controller has loaded all the ajax stuff then you do this:
$scope.ready = true;
It looks like to me what you are really after is a Promise / Deferred:
// for the purpose of this example let's assume that variables '$q' and 'scope' are
// available in the current lexical scope (they could have been injected or passed in).
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() {
if (okToGreet(name)) {
deferred.resolve('Hello, ' + name + '!');
} else {
deferred.reject('Greeting ' + name + ' is not allowed.');
}
});
}, 1000);
return deferred.promise;
}
var promise = asyncGreet('Robin Hood');
promise.then(function(greeting) {
alert('Success: ' + greeting);
}, function(reason) {
alert('Failed: ' + reason);
);
Edit: right, here's a simple example of using a Promise with a Controller and binding:
var app = angular.module('myApp', []);
app.controller('MyCtrl', function($scope, $q) {
var deferredGreeting = $q.defer();
$scope.greeting = deferredGreeting.promise;
/**
* immediately resolves the greeting promise
*/
$scope.greet = function() {
deferredGreeting.resolve('Hello, welcome to the future!');
};
/**
* resolves the greeting promise with a new promise that will be fulfilled in 1 second
*/
$scope.greetInTheFuture = function() {
var d = $q.defer();
deferredGreeting.resolve(d.promise);
setTimeout(function() {
$scope.$apply(function() {
d.resolve('Hi! (delayed)');
});
}, 1000);
};
});​
Working JSFiddle: http://jsfiddle.net/dain/QjnML/4/
Basically the idea is that you can bind the promise and it will be fulfilled once the async response resolves it.

Resources