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).
Related
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();
}
});
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.
This is a simple problem but seems tricky due to asynchronous nature of promises.
With in my data service, I want to ensure that data is retrieved back from the server before moving on to next step as other functions/clients depend on that data.
I don't want clients to register callbacks or use .then() and certainly not use $timeout when requesting the data.
How can I make sure that the controller and service that depend on my Data service get the data right away when requested? I've explained my problem using the code below. I would very much like to continue on my current approach if possible.
AngularJS Version: 1.4+
My Current Approach
//Data Service
App.factory('HealthyFoodService', function($resource, $q) {
var fruitsPromise = $resource('api/food/fruits', {}).query().$promise;
var veggiesPromise = $resource('api/food/veggies',{}).query().$promise;
var fruitsData, veggiesData;
//Ensure $q populates the data before moving on to next statement.
$q.all([fruitsPromise, veggiesPromise]).then(function(data) {
fruitsData = data[0];
veggiesData = data[1];
}
function getCitrusFruits() {
var citrusFruits;
var allFrutis = fruitsData;
//code breaks here because fruitsData is still undefined when called from a controller or another service.
//some logic for this function
return citrusFruits;
}
function getLeafyVeggies() {
var leafyVeggies;
var allVeggies = veggiesData;
//code breaks here because veggieData is still undefined when called from a controller or another service.
//some logic for this function
return leafyVeggies;
}
function getLeafyVeggyByName(name) {
//this function is called from other services and controllers.
var leafyVeggies = getLeafyVeggies();
return leafyVeggies[name];
}
return {
getCitrusFruits: getCitrusFrutis,
getLeafyVeggies: getLeafyVeggies,
getLeafyVeggyByName: getLeafyVeggyByName
});
Below are the two clients. One is a controller and another is a service. They both need the data right away as following statements depend on the returned data.
//Controller
App.controller('LeafyVeggieController', function(HealthyFoodService) {
//Ideally I just'like to do something like below instead of calling `.then()` and registering callbacks.
var leafyVeggies = FoodService.getLeafyVeggies();
//leafyVeggies is undefined because data is not available yet;
});
//Another service depending on HealthyFoodService- similar scenario
App.factory('LeafyVeggieReportService', function(HealthyFoodService) {
function generateLeafyVeggieReport() {
var name = 'spinach';
var veggieInfo = HealthyFoodService.getLeafyVeggieByName(spinach);
//veggieInfo is undefined
//logic that need data.
});
My Previous Approach
Below is how I had it partially working before but I wasn't happy about using .then() everytime I needed the data.(Even with in the same service)
App.factory('HealthyFoodService', function($resource, $q) {
//resource variables;
function getLeafyVeggies() {
return $q.all([veggiesPromise]).then(function(data) {
//logic
return leafyVeggies;
});
}
function getLeafyVeggieByName() {
var leafyVeggies = getLeafyVeggies().then(function(data) {
return data;
}
//some logic
//still causes issues when called from another service because above call doesn't get the data right away.
}
return {
getLeafyVeggies: getLeafyVeggies,
getLeafyVeggieByName: getLeafyVeggieByName
}
//controller
App.controller('LeafyVeggieController', function(HealthyFoodService) {
var leafyVeggies = HealthyFoodService.getLeafyVeggies().then(function(data) {
return data;
});
//controller related logic
});
Update
I'm using ui-router as well, so I'm aware that I can use resolve:{} in $stateProvider to inject the data directly into the controller. The puzzle is how to get the data when I make a request from another service or from another function with in the same service without having to use .then().
Solution
Using $q.all([]) in my client services that depend on my Data service has done the trick for me. I have used $q.all([]) whenever I'm in a situation where I need all the data to be present before start processing the logic.
I still have to use .then() on my clients, but by using $q.all([]), I can still slightly simulate a synchronous flow without breaking any asynchronous principles.
I don't think this is possible. There is inherent latency in network operations that you need to wait for. Not doing so results in the application continuing before the data is available.
That being said, most of the native model binding operations will implicitly wait on promises, so there would be no need to .then if no further manipulation of the data is necessary before passing to the view. You can also use the transformResponse method of ngResource to help with this.
Another option might be to shift the complexity to the resolve methods in the route config. In that case you would handle the .then in the resolve and pass the resolved data to your controller. This will keep your controllers cleaner, but still requires you resolve the promises in the route config.
Try having the service hold your data and have the controller reference that data, instead of trying to pass it to your controller scope. Resolve the promise inside the service like so:
App.factory("HealthyFoodService", function($resource,$q) {
var service = {};
service.data = {
fruitsData: [],
veggiesData: []
}
$resource('api/food/fruits', {}).query().$promise.then(function(data) {
$service.data.fruitsData = data[0];
$service.data.veggiesData = data[1];
})
service.getCitrusFruits = function() {
var citrusFruits;
// Perform some logic on service.data.fruitsData
return citrusFruits;
}
return service;
})
In you controller, you can talk to the service like so:
App.controller("FruitController", function($scope, HealthyFoodService) {
// Now you can access the service data directly via $scope.hfs.fruitsData
$scope.hfs = HealthyFoodService.data;
// Or we can create a local copy of the data using the getCitrusFruits function
$scope.citrusFruits = HealthyFoodService.getCitrusFruits();
})
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.
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.