Angularjs situations where something doesn't work without timeout - angularjs

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).

Related

Angular generic directive/controller to disable button after detecting any ajax request

i've seen many posts about this subject, but not specificly about this question.
I'm wondering if there could be a generic directive/controller in AngularJs to disable a button (that calls an ajax request) and re-enable it after the request ends.
I've used the word "generic" because i've seen some solutions using a callback after a specific ajax request, but it's not what i need.
I need that:
when clicking on a button, and the button calls an ajax request, it becomes disabled until the request ends.
Thank you for your help
Here is a possibility.
You can think of http service calls just as a promise. Once a http service call is fulfilled then it will call the next promise in the chain, if there is one.
You can receive a funcion in a directive, then do a wrapping call to it and chain another function to it. So this way you know when the promise is being executed and when it's fulfilled.
You need to be sure to retun the promise in the controller. Then pass that funcion to the directive.
Check the following example
https://codepen.io/bbologna/pen/zdpxoB
JS
var app = angular.module('testApp', []);
app.factory('fakeHttpService', ['$timeout', function($timeout){
var doSomething = function(value) {
return new Promise(function(resolve, reject) {
$timeout(() => { resolve(value + 1); $timeout() }, 1000);
})
}
return { doSomething: doSomething }
}])
app.controller('sampleController', ['$scope', 'fakeHttpService',
function($scope, fakeHttpService) {
$scope.doSomething = function(value){
return fakeHttpService.doSomething(value);
}
}
])
app.directive('buttonAsync', function(){
return {
restrict: 'E',
template: `<button ng-click="excecute()" ng-disabled="disabled">DO</button>`,
scope: {
on : '&'
},
controller : ['$scope', function($scope) {
$scope.disabled = false;
$scope.excecute = function() {
$scope.disabled = true;
$scope.on()
.then(function() {
$scope.disabled = false;
})
}
}]
}
})
Html
<div ng-app="testApp" ng-controller="sampleController">
<button-async on="doSomething(3)"></button-async>
</div>

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;});

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

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.

Angular directive remove itself

I have a gallery of images which each pull in related data. I've made a directive to lazy load the images once they are in view. It works well, but each of the directives continues watching for the scroll event, with around 200 of them, it's a lot of events being fired unnecessarily. Is there a way to remove the directive, or disable it?
app.directive('lazyLoadGallery', function(resourceService, $rootScope){
return{
link: function (scope, element, attrs) {
var isLoaded = false;
$('.issue-gallery').on('scroll', function(){
console.log($rootScope.number);
if((attrs.lazyLoadGallery/10) % 1 === 0 && !isLoaded) {
if($(element).visible()){
isLoaded = true;
resourceService.issueListPages.list({number:$rootScope.number}).$promise.then(function(data){
$rootScope.issueList = $rootScope.issueList.concat(data.results);
$rootScope.number ++;
$(element).removeAttr('lazy-load-gallery');
});
};
}else{
$(element).removeAttr('lazy-load-gallery');
}
})
}
}
});
my attempt was to remove the attribute from the DOM. Even though it is removed the directive still is watching for scroll events and working as if it wasn't removed.
I was unable to $destroy listeners on the parent object without eliminating it's event for all directives. I came up with a name spaced event which cleans up listeners.
app.directive('lazyLoadGallery', function(resourceService, $rootScope, $compile){
return{
controller: function($scope, $element, $attrs) {
var isLoaded = false;
angular.element('.issue-gallery').on('scroll.'+ $scope.$id, function () {
if (($attrs.lazyLoadGallery / 10) % 1 === 0 && !isLoaded) {
if ($($element).visible()) {
isLoaded = true;
resourceService.issueListPages.list({number: $rootScope.number}).$promise.then(function (data) {
$rootScope.issueList = $rootScope.issueList.concat(data.results);
$rootScope.number++;
angular.element('.issue-gallery').off('scroll.'+ $scope.$id);
});
}
;
} else {
angular.element('.issue-gallery').off('scroll.'+ $scope.$id);
}
})
}
}
});
The angular documentation states that the return of $on function is
Returns function() Returns a deregistration function for this
listener.
So in your case just assign it to a variable and call it when you don't need it anymore.
var deregister = $scope.$on('something', function() {
if (success) {
deregister();
}
});

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">

Resources