I'm not really sure about this issue but it seems that sometimes when I activate $watch for a function then it doesn't work.
for example I have this simple service
angular.module('sp-app').factory('mediaSources', function() {
var storages = [];
return {
addStorage: function(storage) {
storages.push(storage);
},
getStorages: function() {
return storages;
}
}
});
and when I watch getStorage method in order to update my view it doesn't call change callback or calls only at initialization stage
$scope.$watch(function($scope) {
return mediaSources.getStorages();
}, function() {
console.log('call')
});
and I can only track changes by watching length property of returned array
return mediaSources.getStorages().length;
and I wonder because I have written similar think somewhere else within my application and it works fine.
If i interpret what you are trying to do, you should not need to set a watch on something like this, you can just use a factory like so :
angular.module('app').factory('mediaSources', function(){
var storages = {};
storages.list = [];
storages.add = function(message){
storages.list.push(message);
};
return storages;
});
then in the controller you want to receive/update the data to for instance, you would do
$scope.myControllerVar = mediaSources.list;
No need to watch over it, it should update for you.
You will have to set up watcher with equality flag as the third argument:
$scope.$watch(function($scope) {
return mediaSources.getStorages();
}, function() {
console.log('call');
}, true);
Related
Simple task here, but not sure about the mistake.
My service:
app.factory('Progress', function () {
var data = {
progressPercentageLoaded: 0
};
return {
getProgress: function () {
return data.progressPercentageLoaded;
},
setProgress: function (progress) {
data.progressPercentageLoaded = progress;
}
};
});
I have one controller that is uploading a file, this one sets the progress value.
//in my controller
$scope.progressPercentageLoaded = {progress:0};
//a few lines down
function (evt) {
console.log(evt);
$scope.progressPercentageLoaded.progress = evt.loaded;
Progress.setProgress($scope.progressPercentageLoaded.progress);
My second controller should simply watch the service for changes and update the view, but it stays stuck at zero even though I confirm the upload is happening and that evt.loaded is changing.
$scope.progress = 0;
$scope.$watch(function () { return Progress.getProgress(); }, function (newValue, oldValue) {
if (newValue !== oldValue) {
$scope.progress = Math.min(100, parseInt(100 * newValue / $scope.size));
if($scope.progress == 100)
{
$scope.progressModalInstance.close();
window.location.reload();
}
}
});
That is, $scope.progress in second controller should update with the value of evt.loaded in the first controller, but it doesn't.
Thanks in advance for any guidance.
EDIT: I even added the third watch parameter as true but that didn't help either.
EDIT 2 : The code above actually works to the best of my knowledge, I believe something else was causing a problem as when I reverted the code to the above after editing it due to the answers, it suddenly worked like it should. Sorry about this.
using $rootScope.$broadcast will work better for this
app.factory('Progress', function ($rootScope) {
var data = {
progressPercentageLoaded: 0
};
return {
getProgress: function () {
$rootScope.$broadcast('Event');
return data.progressPercentageLoaded;
},
setProgress: function (progress) {
data.progressPercentageLoaded = progress;
}
};
});
In your second controller instead of using watch
something like
$rootScope.$on('Event', function(){
//your logic here
})
You are losing the reference because you are watching the Int value that you are updating every time, hence you are changing the reference. You have to watch the whole object progressPercentageLoaded.
You have to pass true as the last parameter of the $watch function so that the equality check is angular.equals. Otherwise, only reference equality is checked.
I'm not 100% sure, but Angular may not be aware of the file upload event. Try calling $apply:
$scope.$apply(function() {
Progress.setProgress($scope.progressPercentageLoaded.progress);
});
I tried to follow along with this example but my code never enters the callback with the newValue when the input changes for some reason. The only difference in my example and the example bin below is that I'm using a service to hold the value instead of a controller variable.
I've been trying to make it work but I can't wrap my head around it. What am I missing?
http://jsbin.com/yinadoce/1/edit?html,js,output
Note that I've excluded the input and the steps prior to the value being updated in the service as this works perfect. The issue is only that my watch doesn't understand when the value has changed in the service.
EDIT: Forgot to inject $scope in the controller when I pasted over the code, now it's complete.
Controller:
coForms.controller('CoFormsCtrl', ['$scope', 'coFormsInfo', function($scope, coFormsInfo) {
$scope.$watch(angular.bind(coFormsInfo.getInfo(), function() {
return coFormsInfo.getInfo();
}), function(newVal) {
console.log(newVal);
});
}]);
Service:
coForms.service('coFormsInfo', [function() {
var info = {
filteredList: []
}
this.setFilteredList = function(list) {
info.filteredList = list;
};
this.getInfo = function() {
return info;
};
}]);
The watcher is there to detect any changes in the variable you're watching. How can he watch something that is not... Strictly present like a return value?
I'm not sure about what I'm saying because I'm new to angular, but the logic seems false there. You need to watch something declared to detect some changes.
You should call your service to get your infos when you need them and watch for an info variable.
EDIT
My bad there is something like that but you should declare it in a function maybe like the example on the documentation
var food;
scope.foodCounter = 0;
expect(scope.foodCounter).toEqual(0);
scope.$watch(
// This function returns the value being watched. It is called for each turn of the $digest loop
function() { return food; },
// This is the change listener, called when the value returned from the above function changes
function(newValue, oldValue) {
if ( newValue !== oldValue ) {
// Only increment the counter if the value changed
scope.foodCounter = scope.foodCounter + 1;
}
}
);
I've been trying to watch a service object from a controller. I've been trying to solve this problem in a nice way but it has been imposible. the only solution I could find is this one (see the code). The thing is when I "console.log" the returned value, I can see the object but I can not access to the properties. it says "undefined". So the only solution I could find is this one but I don't like it too much. Any ideas???
This is my "watcher" of my controller:
$scope.$watchCollection(function(){return angular.toJson(measuresServ.getFinalMeasuresVal())},function(newVal, oldVal) {
$scope.measures = JSON.parse(newVal);
console.log($scope.measures);
console.log($scope.measures.temperatura);
});
This is the object of my service:
var finalMeasures = {};
return {
getMeasures : function(){
finalMeasures.temperatura = 24;
finalMeasures.humedad = 45;
},
getMeasuresVal: function(){
return finalMeasures;
}
}
I think it should work:
$scope.$watch(function () {
return measuresServ.getFinalMeasuresVal();
}, function (newVal, oldVal) {
console.log(newVal);
}, true);
I'm building an app in angularjs, where I have a central notification queue. Any controller can push into the queue and digest the messages.
I have built a service like:
angular.module('app').factory('notificationSvc', ['translateSvc', notification]);
function notification(translate) {
var notificationQ = [];
var service = {
add: add,
getAll: getAll
};
return service;
function add(message, type) {
notificationQ.push({
message: message,
type: type
});
}
function getAll() {
return notificationQ;
}
}
(One of the problems with this is that the notificationQ can be modified unsafely by calling svc.getAll()[3].message = "I have changed a message"; or something similar. I originally wanted a "push only" service with immutable messages, but this problem is outside of the scope of this question.)
If I digest this queue in a controller like:
$scope.notifications = svc.getAll();
$scope.current= 0; // currently visible in the panel
And use it like:
<div ng-repeat="notification in notifications" ng-show="$index == current">
<p>{{notification.message}}</p>
</div>
I can bind to it, see it changing and all is well. I can cycle through past notifications by changing the variable current.
The question:
When the queue gets a new element I want the $scope.index variable to change to notifications.length - 1. How do I do that?
I have seen examples using $rootScope.$broadcast('notificationsChanged'); and $scope.$on('notificationsChanged', function() { $scope.index = $scope.notifications.length - 1; });, but I did not really like the pattern.
I have a controller that knows about the service, has a direct reference to it, and yet we use $rootScope to communicate? Everything else sees the $rootScope, and all the events from different services will clutter up there.
Can't I just put the event on the service instead? Something like this.$broadcast('notificationsChanged') in the service and svc.$on('notificationsChanged', function() { ... }); in the controller.
Or would it be cleaner to watch the data directly? If yes, how? I don't like this as I was not planning on exposing the full array directly (I was planning on get(index) methods) it just sort of happened along the lines where I had no idea what I was doing and was happy that at least something works.
You could just manage events yourself. For example (untested):
function EventManager() {
var subscribers = [];
var service = {
subscribe: subscribe;
unsubscribe: unsubscribe;
publish: publish
}
return service;
function subscribe(f) {
subscribers.push(f);
return function() { unsubscribe(f); };
}
function unsubscribe(f) {
var index = subscribers.indexOf(f);
if (index > -1)
subscribers.splice(index, 1);
}
function publish(e) {
for (var i = 0; i < subscribers.length; i++) {
subscribers[i](e);
}
}
}
function notification(translate) {
var notificationQ = [];
var addEvent = new EventManager();
var service = {
add: add,
getAll: getAll,
onAdded: addEvent.subscribe;
};
return service;
function add(message, type) {
var notification = {
message: message,
type: type
};
notificationQ.push(notification);
addEvent.publish(notification);
}
function getAll() {
return notificationQ;
}
}
Then, from your controller:
...
var unsubscribe = notificationSvc.onAdded(function(n) { /* update */ });
Caveat: using this method the service will maintain a reference to the subscriber function that is passed to it using subscribe, so you have to manage the subscription using $scope.$on('$destroy', unsubscribe)
The notification approach would definitely work. Depending on your implementation it would be the right solution.
Another approach would be to watch the notifications array in your controller, like this:
$scope.$watchCollection('notifications', function(newValue, oldValue) {
$scope.index = newValue.length - 1;
});
This should work, because your controller receives a direct reference to the notifications array and therefore can watch it directly for changes.
As runTarm pointed out in the comments, you could also directly $watch the length of the array. If you're only interested in length changes this would be a more memory saving approach (since you don't need to watch the whole collection):
$scope.$watch('notifications.length', function (newLength) {
$scope.index = newLength - 1;
});
I am writing a custom filter which involves needing some data map to be initialised so that the data map is not created everytime the filter is invoked.
I do this:
myModule.filter("myfilter", function($filter) {
return function(letter) {
// ...
var blah = myOtherFunction(letter)
}
}
var myOtherFunction = function() {
// initialise some data
var myData = {
"a":"letterA"
"b":"letterB"
}
return function(letter) {
return myData[letter];
}
}();
This means the file where I define my filter has a utility function which uses a closure to close over data which is initialised once and once only.
I am wondering is there a more angular way to achieve this?
Thanks
In general, data should be manipulated/fetched/sent and shared throught services.
But if the "data" you are referring to are:
1.) static and
2.) specific to the logic of the filter
then I believe it does not fall into the general category of "application data"; it is rather "filter logic" stuff.
As such their place is right in the filter.
(BTW, in order to initialize it only once, you don't need all that complex "calling IIFE returned function" stuff. Just put the data in the filter definition function (see below).)
app.filter("myFilter", function() {
var staticFilterSpecificData = {
"a": "letterA",
"b": "letterB"
};
console.log('Initialized only once !');
return function(letter) {
return staticFilterSpecificData[letter] || 'unknown';
};
});
See, also, this short demo.
If your data are static, just create a new module/service and inject it in your filter :
myModule.filter('myfilter', ['myService', function(myService) {
return function(amount, currency) {
return myService.getLetter("a"); //don't know what you want to do here, using amount or currency I guess ?
};
}]);
myModule.factory('myService', function() {
var service = {};
var myData = {
"a":"letterA"
"b":"letterB"
}
service.getLetter = function (letter) {
return myData[letter];
}
return service;
});
If your data are retrieved asynchronously, follow this post : Asynchronously initialize AngularJS filter