AngularJS losing reference to service variable on re-assignment? - javascript

I didn't know a better way to phrase that question. I've written a basic service to be used by two controllers.
JsFiddle: http://jsfiddle.net/aditya/2Nd8u/2/
Clicking 'notify' works as expected; it adds a notification to the array. But 'reset' breaks it. Clicking either button after 'reset' doesn't do anything. Does anyone know what's going on here?
PS. I think it has something to do with Angular losing the reference since notifs is being re-assigned (technically), so I wrote getters and setters, but even emptying the array involves pop()ing till it's empty, which doesn't seem very efficient.
Plunkr if JsFiddle is down: http://plnkr.co/edit/mzfLLjFXCwsxM5KDebhc

I've forked your plunker and propose a solution:
In reset function, try to remove the objects of the array instead of declaring as new empty array:
notificationService.notifs.splice(0, notificationService.notifs.length);
Or, like suggested by #Wizcover:
notificationService.notifs.length = 0
This will notify angular that are modifications in the original array.

I changed your service to this:
.factory("notificationService", function(){
var notifications = [];
return {
notifs: notifications,
clear: function(){
angular.copy([], notifications);
},
get: function(){
return notifs;
}
}
})
and your controller :
$scope.reset = function(){
console.log("reset");
notificationService.clear();
console.log(notificationService);
}
and it works for me.
Naturally it should be a little bit tidier, in that instead of notifs you should have a get, and add and a remove method, but i just wanted to show you where the code changed.
The angular.copy is the method that makes sure the changes are made within angular's lifecycle.
As you can't bind variable but only methods, you could do this:
$scope.getNotifications = notificationService.get;
That should work.

Related

Meteor Method Endlessly Runs

I have this code in a Meteor.methods definition:
update_field: function(collection,document_id,field,value) {
obj = {};
obj[field] = value;
console.log(obj);
if (collection == 'clients') {
var Collection = Clients;
} else if(collection = 'sites') {
var Collection = Sites;
}
Collection.update(
{
_id: document_id
}, {
$set: obj
}, function(error,id) {
console.log(error,id);
return(error,id);
}
);
}
This method is called from several client-side helpers events, and updates the field as needed. But whenever it runs once, it never stops running. Sometimes it runs infinitely even when all the Meteor.call('update_field')s have been commented out. I have tried including a 'caller' parameter and adding that to all the possible calls to figure out why it keeps getting called to no avail. Any ideas why this is looping?
Edit: this runs 2,000/minute
Edit2: this is called in one of two ways: on a keyup code==13 (enter) in an appropriate field or a field blur. However, event when these calls are commented out, the issue persists.
Especially your second comment worries me:
However, even when these calls are commented out, the issue persists.
Then who is calling it? The behaviour you're describing points to some helper executing the method. The method changes some data, which re-executes the helper (reactivity) and we end up with a classic endless loop.
Check your entire source code for references to this method:
$ grep -r "update_field" *
Maybe you set a variable somehow and then use the variable to call the method. Also: Have you declared the method inside a Meteor.methods({ ... }) block?
I think the issue was that one of my methods blurred the input field but preventDefaulted. Then the blur handler was called and caused the loop from there. This is my first Meteor project, so I'm chalking this one up to not quite understanding the system sufficiently. I still find it strange that the method was getting called when the callers were commented out, but I'll figure that one out another day.

How to animate unchanged ng-repeat with AngularJS

I have a template that looks like this:
<p ng-repeat="item in myobj.items" class="toAnimate">{{item}}</p>
and I would like to use the animate module do a jQueryUI addClass/removeClass animation on the element using the JavaScript method described in the docs:
ngModule.animation('.toAnimate', function() {
return {
enter: function(element) {
element.addClass('pulse').removeClass('pulse', 2000);
}
};
});
This works beautifully, but the problem is that, since I want to use the p.toAnimate element to display status messages, it will not change the content according to angular.
To break it down a little further, say I have a name field. When I click Save the message Name was saved successfully. is displayed. Now if I modify the name and click save again, assuming the save was successful, the message should be re-displayed to give the user feedback of the newly edited name. The pulse does not happen, however, because the items in myobj.items didn't technically change.
I realize that I could remove the item after a period of time (and that is probably the route I will take to implement the real solution), but I'm still interested to see if this sort of thing can be done using AngularJS.
What I want to do is register with angular that the message should be treated as new even though it is not. Is there any way to do this?
A fiddle to go along with this: http://jsfiddle.net/Jw3AT/
UPDATE
There is a problem with the $scope.$$phase approach in my answer, so I'm still looking for the "right" way to do this. Basically, $scope.$$phase is always returning $digest, which causes the conditional to fail. Removing the conditional gives the correct result in the interface, but throws a $rootScope:inprog.
One solution I found is to add a $apply in the middle of the controller function:
$scope.updateThingy = function () {
$scope.myobj.items = [];
if (!$scope.$$phase) {
$scope.$apply();
}
$scope.myobj.items = ['Your name was updated.'];
};
Updated fiddle: http://jsfiddle.net/744Rv/
May not be the best way, but it's an answer.

Backbone.js getting a list to ensure only one instance of a certain model exists

Forgive me, I am new to backbone, and the MVC javascript concept.
So, I am making a comments system:
createComment: function () {
// create new comment model
var comment = new CommentModel({});
// render form view right after new button
var formview = new FormView({model: comment});
this.$el.after(formview.render().$el);
// add saved model to collection after form was submitted successfully
formview.on('success', this.handleFormSuccess, this);
// finally, return false to stop event propagation
return false;
},
What I can't understand is how to get a list of comments which have been rendered, but have not been sent to the collection. See, I want to ensure that only one comment box is opened at once.
My approach is to do a check to see how many comments are open, and close everyone except the current model.
Using Backbone.js & Underscore, how to get a count of items from the model? seems to give advice for how to do this after the model hits a collection.
I am very new with backbone, so it is entirely possible I am in the exact wrong direction with this.
How do I get the list?
As Joe suggested, I think your problem is this line:
formview.on('success', this.handleFormSuccess, this);
However, I don't think his suggestion (of changing "success" to "sync") will work either, because formview is a View, not a Model or Collection, so it doesn't even have an on method.
What does have an on method is the view's element, so you can do:
formview.$el.on('success', this.handleFormSuccess, this);
Two problems with that though:
jQuery is lame and doesn't let you set the context like that
"success" isn't a form event; you want this code to trigger on "submit"
so to fix those two issues you need to change the line to:
formview.$el.on('submit', _(this.handleFormSuccess).bind(this));
Alternatively you could also call:
_(this).bindAll('handleFormSuccess');
in FormView's initialize, which would make it so that you don't need to bind this.handleFormSuccess):
formview.$el.on('submit', this.handleFormSuccess);
Hope that helps.

AngularFire Collection Length

I'm attempting to get the length of the array of a simple angularFireCollection and can't seem to:
var stf = new FireBase("http://myfirebase-app.firebaseio.com/staff");
function staffListCtrl($scope, angularFireCollection){
$scope.staff = angularFireCollection(stf);
console.log($scope.staff.length);
}
The output in the console says:
0
Which I know is incorrect. It should be returning somewhere around 5 as the length (see screenshot below for the output of $scope.staff.
Any help is a appreciated as I can't seem to get past this absolutely, utterly simple JS task.
In this case, the correct way to print the number of retrieved elements within the callback would be following:
var stf = new FireBase("http://myfirebase-app.firebaseio.com/staff");
function staffListCtrl($scope, angularFireCollection) {
$scope.staff = angularFireCollection(stf, function() {
console.log(stf.numChildren());
});
}
The reason for this is that the initial callback function is called before actual assignment to $scope.staff takes place. So within the callback function you can access only the Firebase DataSnapshot object stf. Hope this helps.
You're trying to access the length immediately after calling angularFireCollection, but the actual data is retrieved over the network and therefore it takes a little while for the array to be updated. You can pass a function as an argument to angularFireCollection to be notified about when the initial data is loaded, like so:
var stf = new FireBase("http://myfirebase-app.firebaseio.com/staff");
function staffListCtrl($scope, angularFireCollection) {
$scope.staff = angularFireCollection(stf, function() {
console.log($scope.staff.length);
});
}
ah, I see it in angularfire.js. Line 298 wraps adding an item in a $timeout. That's causing the initalCb() to get called before the data has been added to the collection. I pulled the $timeout and it worked. However, then I had to call $scope.$apply() to reflect the added items. I ended up passing scope into angularFireCollection to be sure $apply() gets called. No idea what else I broke by pulling the timeout.
this is a view of the issue: plunkr
EDIT:
as far as I can tell and showed in the plunkr, this doesn't work.
$scope.staff = angularFireCollection(stf, function() {
console.log($scope.staff.length);
});
and while pulling the $timeout from angularfire.js did fix that particular issue, it caused all kinds of other headaches with databinding(as expected), so I put it back. It seems the way to go is to use the snapshot that is passed in the callback. I can't find a lot of documentation on it, but here's what worked for me:
$scope.staff = angularFireCollection(stf, function(snapshot) {
angular.forEach(snapshot, function(item){
//will let you iterate through the data in the snapshot
});
});

How do I bind a function to a change in an object's data member in Javascript?

I'm working on a project in JavaScript where we're building a Greasemonkey plugin to an organizational site we're using in our office. We're having trouble getting our changes to stay rendered, since we can't simply inject our changes into the existing render function.
As a result, we need to find every event where rendering happens and inject our own render function there. However, there are some events that we can see happening, but we can't hook into them. What I'd like to know is how to bind a function to an object's data member, so that the function is called whenever that member changes. One of our team members seemed to think it was possible, but the method he told us to use didn't seem to work.
What we tried was something along the lines of
window.Controller.bind("change:idBoardCurrent", OMGITWORKED);
where idBoardCurrent is a member of window.Controller and OMGITWORKED is the function we'd like to be called when window.Controller.idBoardCurrent is changed.
I'm not very familiar with JavaScript or data binding, so I have no idea if this is right or wrong, or what is correct or incorrect about it. If someone could point out what to change in this snippet, or if they could suggest another way to go about this, I would be very appreciative.
You can use Object.defineProperty to define a setter and getter for the Objects property
Object.defineProperty(window.Controller,"idBoardCurrent",{
get : function() { return this.val; },
set : function(value) {this.val = value;OMGITWORKED(value); }
});
function OMGITWORKED(param) {
console.log("idBoardCurrent has been Changed to " + param);
}
window.Controller.idBoardCurrent = "Test";
window.Controller.idBoardCurrent = "Test2";
console.log(window.Controller.idBoardCurrent)
Edit: changed the code according to the contexts object
JSBin
As this is specifically Firefox, you can use the mutation events it provides. But note the caveats on them from that page:
The W3C specification for them was never widely implemented and is now deprecated
Using DOM mutation events "significantly degrades" the performance of DOM modifications
If you're able to restrict yourselves to Firefox 14 and higher, you can use the new mutation observers stuff instead.
This is, when I am not totally wrong, more a question of javascript.
I found some information about that topic
Listening for variable changes in JavaScript or jQuery
jQuery trigger on variable change
Javascript Track Variable Change
Sorry when I didn't understand the topic.
All the best

Categories

Resources