Using $scope.$watch and still need $scope.$apply? - javascript

I've implemented a filedrop directive that puts the file dropped in ngModel.
<filedrop data-ng-model="file"></filedrop>
I'm using the following code in my controller:
$scope.$watch('file', function(newVal, oldVal) {
if (newVal) {
var reader = new FileReader();
reader.onload = function(event) {
$scope.parseFile(newVal);
};
reader.readAsDataURL(newVal);
}
}, false);
In $scope.parseFile Im actually parsing the XLSX:
$scope.parseFile = function(file) {
xlsxParser.parse(file).then(function(data) {
console.log("Number of columns", data.datasheet[1].length);
console.log("Number of rows", data.datasheet.length);
$scope.validationErrors = [];
$scope.brand = {
items: []
};
$scope.dataItems = [];
$scope.datasheetValidate(data.datasheet, $scope.brand);
$scope.datasheetData(data.datasheet, $scope.dataItems);
if ($scope.validationErrors.length == 0) $scope.validationErrors.push("Nice work, no validation errors");
//$scope.$apply(function(){});
}, function(err) {
console.log('error', err);
});
}
As you can see, I commented out the //$scope.$apply(function(){}); in the body....
BUT, I need it in order to have my web page updated with the scope changes (e.g. show the validationErrors)
How come I need the $scope.$apply?

It doesn't matter that you're using $scope.$watch. That doesn't help when you're calling $scope.parseFile from inside reader.onload callback, which is outside of Angular realm.
$scope.$apply should be used inside the callback itself:
reader.onload = function(event) {
$scope.$apply(function(){
$scope.parseFile(newVal);
});
};

$scope.$watch and $scope.$apply are complementary.
$scope.$watch registers a new watcher on the scope. Whenever there's a digest cycle, the watch function runs.
$scope.$apply triggers a digest cycle--that is to say, if nothing ever called $scope.$apply, no watchers would ever be run at all.
For built-in directives that deal with user input (ng-click, ng-keydown, etc.) and built-in services ($http, $location, $timeout, etc.), Angular calls $scope.$watch for you. However, any time you deal with asynchronous code that lives outside of these built-in directives or services, you must tell Angular that it should kick off a new digest cycle by calling $apply yourself.
As mentioned by Stewie, you should try to keep your $apply calls as close to the asynchronous operation as possible; in this case, it means using it at the very top level of the onload callback.

The only reason i see this happen if xlsxParser.parse(file).then is not a angular promise callback method.
And as confirmed by #Sander this indeed is not a angular promise, so option here is to keep using the existing $scope.$apply call.

Related

AngularJS: passing a function along with scope to factory [duplicate]

$scope is not working inside a callback function.
angular.
module('common').
controller('bidVBoxController', ['$scope', '$location','$element', 'Socket', 'Bid',
function($scope, $location, $element, Socket,Bid){
var self = this;
self.socket = new Socket("ws://192.168.225.59:8010/ws/bidData/");
$scope.placeBid = function(){
self.socket.send({
type: "place_bid",
data: {
bidId: $scope.bid.id
}
});
};
console.log($scope.bid);
$scope.bid.top_bid_data="sss";//This works.
self.socket.onmessage(function(event) {
var data = JSON.parse(event.data);
console.log($scope.bid);//This doesn't work
$scope.bid.top_bid_data=data["message"];//This doesn't work
});
}])
A callback function is passed to the self.socket.onmessage, which is supposed to update $scope variable. But it appears that doesn't work. Please help.
Update1:
This controller is used with a directive bidVBox and there are multiple elements:
<bid-v-box ng-repeat="bid in bids">
</bid-v-box>
When the callback function is executed in the first bid-v-box element's controller, $scope.bid.top_bid_data=data["message"]; updates the scope of the last element and not the first one. I have also tried using $scope.$apply(function(){$scope.bid.top_bid_data=data["message"];}). But that didn't work.
It would be wiser to move the websocket constructor to a service and use only one connection. As written the ng-repeat is creating multiple websocket connections to the server. It is up to the server side application to treat each connection differently. Evidently the server is responding to the last connection made instead of each individually. For more information, see Stackoverflow: Multiple Websockets.
See also Github WS Issue #684 - Multiple connections, but single websocket.on("message") event emitter

How to stop $watch when change the object vlaue on second time

How to stop $watch while changing the object
Here is a $watch function
$scope.$watch($scope.OneTime,function(old,new)
{
// my function
});
The above $watch function will be fire whenever my (OneTime) object value has been changed.
But I won't to watch the object on every change, I just want to fire the $watch function when I change the my object on first time only.
I also tried something and find out a function from angular.js script file But I don't know what the below function doing exactly.
You can find this function from angular.js script file
function oneTimeWatchDelegate(scope, listener, objectEquality, parsedExpression) {
var unwatch, lastValue;
return unwatch = scope.$watch(function oneTimeWatch(scope) {
return parsedExpression(scope);
}, function oneTimeListener(value, old, scope) {
lastValue = value;
if (isFunction(listener)) {
listener.apply(this, arguments);
}
if (isDefined(value)) {
scope.$$postDigest(function () {
if (isDefined(lastValue)) {
unwatch();
}
});
}
}, objectEquality);
}
But am seeing a pretty word unwatch();inside the function . So i think I need to use $unwatch for the object when end of the $watch function. But I couldn't get anything about $unwatch concept anywhere in angular document. but I can see it on angular script.
I had some idea about manually stop this $watch function by this below way
var unwatch = $scope.$watch("OneTime", function() {
//...
});
setTimeout(function() {
unwatch();
}, 1000);
But I am thinking about if angular provide to unwatch function to stop the abject watching, it would be easy to handle in my whole application. So planed to take override something in angular.js file in my application. let me know if you have any idea about override angular.js script file to create $unwatch function as same as $watch function. And also let me know angular had any$unwatch function.
I think you need one way binding over here
you can achieve this br
{{::oneTime}}
in your html page One-time expressions will stop recalculating once they are stable, which happens after the first digest
var $unwatch=$scope.$watch('onetime',function(){
unregister();
}
AngularJS does already provide such function, exactly as you mentioned above. When you create a watcher, it returns you a function that may be used to stop watching it.
From the $rootScope.Scope documentation,
$watch(watchExpression, listener, [objectEquality]);
Returns: function() Returns a deregistration function for this listener.
The only thing you need to do to unwatch your object would be calling the returned function. You could call it inside your watch function so it will be executed at the first time your watcher is invoked.
var unwatch = null;
// start watching the object
var unwatch = $scope.$watch($scope.OneTime, function(old, new)
{
// my function
if (unwatch != null) {
unwatch();
}
});

In what order do angular watchers and event listeners execute?

If one changes a scope property first, and then broadcasts an event second, will the corresponding watcher callback and event listeners callback always be executed in that same order? For example:
$scope.foo = 3;
$scope.$broadcast('bar');
and elsewhere:
$scope.$watch('foo', function fn1(){...});
$scope.$on('bar', function fn2(){...});
Will fn1 always be executed prior to fn2, or visa-versa, or can the order not be relied upon? Please cite sources, preferably to official angular docs.
In case it matters: lets assume the $scope.foo= and the $broadcast occur in a function invoked by an ng-click (i.e. user interaction)
[aside] Sorry question title is sloppy - please rename if you have something better.
To understand what is happening, you need to understand Angular's $digest cycle and event $emit and $broadcast functions.
Based on some research, I've also learned that Angular does not use any kind of polling mechanism to periodically check for model changes. This is not explained in the Angular docs, but can be tested (see this answer to a similar question).
Putting all of that together, I wrote a simple experiment and concluded that you can rely on your event handlers running first, then your watch functions. Which makes sense, because the watch functions can be called several times in succession during the digest loop.
The following code...
template.html
<div ng-app="myApp">
<div watch-foo ng-controller="FooController">
<button ng-click="changeFoo()">
Change
</button>
</div>
</div>
script.js
angular.module('myApp', [])
.directive('watchFoo', watchFooDirective)
.controller('FooController', FooController);
function watchFooDirective($rootScope) {
return function postLink(scope) {
scope.$watch(function () {
return scope.foo;
}, function (value) {
console.log('scope.$watch A');
});
scope.$on('foo', function (value) {
console.log('scope.$on A');
});
$rootScope.$on('foo', function (value) {
console.log('$rootScope.$on A');
});
$rootScope.$on('foo', function (value) {
console.log('$rootScope.$on B');
});
scope.$on('foo', function (value) {
console.log('scope.$on B');
});
scope.$watch(function () {
return scope.foo;
}, function (value) {
console.log('scope.$watch B');
});
};
}
function FooController($scope) {
$scope.foo = 'foo';
$scope.changeFoo = function() {
$scope.foo = 'bar';
$scope.$emit('foo');
};
}
...yields the following results in the console when the 'Change' button is clicked:
scope.$on A
scope.$on B
$rootScope.$on A
$rootScope.$on B
scope.$watch A
scope.$watch B
UPDATE
Here is another test that illustrates the watch callback being called twice in the digest loop, but the event handlers not being called a second time: https://jsfiddle.net/sscovil/ucb17tLa/
And a third test that emits an event inside the watch function, then updates the value being watched: https://jsfiddle.net/sscovil/sx01zv3v/
In all cases, you can rely on the event listeners being called before the watch functions.

AngularJS - Recursive function disables $scope.on('destroy')

I've got a recursive function which calls another asynchronous function, and upon resolving the promise, calls itself again after a few seconds:
$scope.gamePolling = function () {
if ($scope.getGames) {
$scope.getGameData().then(function () {
$timeout(function () {
$scope.gamePolling();
}, 3000);
});
}
};
When changing the route / state (using ui-router), I thought the $scope should be destroyed so I could turn off the recursive function using:
$scope.$on('destroy', function () {
$scope.getGames = false;
});
However, on the next page the gamePolling function keeps calling itself because the breakpoint inside the destroy never gets hit.
So my question is why isn't the $destroy event being triggered?
p.s. this also happens when removing the $timeout, so the problem must be with the recursion.
I've gotten around this problem, by turning off gamePolling() in the $stateChangeStart:
$scope.$on('$stateChangeStart', function () {
$scope.getGames = false;
});
So the polling stops but the $destroy event still doesn't seem to be triggered.
As a little test, in state/controller A I assigned the current $scope to a $rootScope variable so I could check if it was destroyed in the state/controller B: $rootScope.testScope = $scope;
When checking $rootScope.testScope.$$destroyed in controller B, it returned true. So it looks like the $scope of the controller A was successfully destroyed. However, in $rootScope.testScope I can still access the variables that were assigned to $scope.
It is "$destroy" event, not "destroy"
It is funny because you mention the event with the right name and in the code you are missing the $ sign prefix.
Hope it helps!
UPDATE: If you allow me I suggest you use an interval instead of a timeout+recursive function. You can then "kill" the interval in the $destroy event handler.

Understanding usage of firebase in Angular service

The following is example code from the official firebase documentation
var app = angular.module("myChatRoom", []);
app.factory("ChatService", function() {
var ref = new Firebase("https://<your-firebase>.firebaseio.com/chat");
return {
getMessages: function() {
var messages = [];
ref.on("child_added", function(snapshot) {
messages.push(snapshot.val());
});
return messages;
},
addMessage: function(message) {
ref.push(message);
}
}
});
app.controller("ChatCtrl", ["$scope", "ChatService",
function($scope, service) {
$scope.user = "Guest " + Math.round(Math.random()*101);
$scope.messages = service.getMessages();
$scope.addMessage = function() {
service.addMessage({from: $scope.user, content: $scope.message});
$scope.message = "";
};
}
]);
I would like to understand what is going on here, as I can't assuredly see why this is actually working.
getMessages() is called only once, when the controller fires. Nevertheless the event bindings will fire even when getMessages() is never called afterwards. As well, the $scope property is updated.
Why is that? Also, is that the recommended way of using firebase with an Angular service/factory?
In getMessages you have the following call:
ref.on("child_added", function(snapshot) {
...
This registers a callback that gets called whenever a child is added to your Firebase chat.
Even when getMessages exits, that callback will remain registered. So that is why you only have to call getMessages once. The name is a bit counter-intuitive, I would probably call it something like registerMessageHandler or registerForNewMessages.
Either way: the callback will be triggered for every added child, until you either turn it off (by calling ref.off("child_added") or until the page reloads. This is known as an asynchronous operation: the callback will happen, regardless of where the original flow is. The flow of the callback and your main code are asynchronous. It is in that sense similar to a regular JavaScript setInterval (which also keeps firing asynchronously, while your main code continues) or a common XMLHttpRequest (which will normally fire after its calling method returns).

Categories

Resources