is it possible to use getReactively on $rootScope variables? - angularjs

I have a case that I want to update the ui based on the user so if a user was log-out and log-in as different user, a score of the user in the ui is changed. The way I thought about doing it is by looking at $rootScope.currentUser.
from my testing it seems like there is some kind of access to the $rootScope, but it is not getting updated on user change (unless I have a bug in my code). here is the relevant code:
$meteor.autorun($scope, function() {
if (null !== $rootScope.currentUser) {
$scope.userForScore = $scope.$meteorObject(Userinfo, {user_id: $scope.getReactively('currentUser')._id});//$rootScope.currentUser._id
}
});
$scope.userScore = function(){
if ($scope.userForScore === undefined || $scope.userForScore.score === undefined) {
return 1;
}
return $scope.userForScore.score;
};
Userinfo - is a collection I created in the server.

Yes it is possible and it works exactly like you did it.
Your problem was unrelated to getReactively, I've created a pull request to your repo to fix it:
https://github.com/Urigo/angular-meteor/issues/415#issuecomment-113925910

You can use Meteor.user() which is a reactive var. No need to add an additional layer of watchers.

Related

How $watch changes of a variable in a service from component's controller?

I have been through all related topics on SO, namely these two:
$watch not detecting changes in service variable
$watch not detecting changes in service variable
are tackling the same issue, but i failed to make it working. Unlike in the above cases, I am using a controller from a component, hence maybe this is related to lacking binding in components, idk. Hope for some experinced assistance.
Have a service:
(function (angular) {
'use strict';
angular
.module('Test')
.service('ShareData', ShareData);
ShareData.$inject = [];
function ShareData() {
let vm = this;
vm.indexes = [];
vm.setIndexes = function(firstIndexParam, lastIndexParam, message) {
if (leaderIndexParam !== undefined || partnerIndexParam !== undefined) {
vm.indexes.mainIndex = firstIndexParam;
vm.indexes.secondaryIndex = lastIndexParam;
vm.indexes.message = message;
}
};
vm.getIndexes = function() {
return vm.indexes;
};
}
})(angular);
It is used in 3 components. Two of them are sending data into the service, the third one uses this data. Sending of data is accomplished in the following way, works:
ShareData.setIndexes(firstIndex, secondIndex, 'update_indexes');
Now here is my problem. In main parent controller i can comfortably access the data by
ShareData.getIndexes();
But my issue is that I need changes in indexes to trigger certain actions in parent controller, so I tried so do as stipulated by relevant questions here on SO:
$scope.$watch('ShareData.getIndexes()', function(newVal) {
console.log('New indexes arrived', newVal);
});
In main controller, I am injecting the service:
TabController.$inject = ['ShareData'];
and using it like:
let indexService = ShareData.getIndexes();
As i said, I can get the data when I am explicitly calling the function. The issue is that it needs to be triggered by the service itself.
It does not work regardless of shamanistic ceremonies a made :) Now, obviously, I am missing something. Should I somehow bind this service to the component, and if yes how is it done? Or maybe the solution is totally dysfunctional and impossible to achieve in my circumstances? An advise is appreciated!
UPDATE: I already have a functional solution with the same service working with $rootScope.$broadcast, however my aim is to get rid of it and not work with the $rootScope.
The problem is that you never actually change the value of vm.indexes - it always points to the same array. setIndexes only modifies properties of this array. That's why $watch, which by default checks for reference equality only, fails to spot the changes.
There are (at least) two ways of solving this: either make $watch check the object equality instead, by adding a third param there:
$scope.$watch('ShareData.getIndexes()', function(newVal) {
console.log('New indexes arrived', newVal);
}, true);
... or (better, in my opinion) rewrite the set function so that it'll create a new instance of indexes instead when there's a change:
vm.setIndexes = function(firstIndexParam, lastIndexParam, message) {
if (leaderIndexParam === undefined && partnerIndexParam === undefined) {
return;
}
vm.indexes = vm.indexes.slice();
Object.assign(vm.indexes, {
mainIndex: firstIndexParam,
secondaryIndex: lastIndexParam,
message: message
});
};
As a sidenote, simply calling setIndexes() does not trigger the digest - and $watch listener only checks its expression when digest is triggered.

NodeJS update user status dynamically with Angular

I have this little issue. I have a service that has a Subscription option. When an item in the DataBase is modified (i.e. customer order), I want the customer to see the change in real-time.
Once the user logs in, he is automatically subscribed to his order's status (or several orders, as the case may be). When the status changes, the service sends a POST to a URL that I designate and when that is done, I want to change the client's order status, however I have no idea how to modify Angular's model in order to change what the user sees.
module.exports = function (app) {
app.route('/api/machine')
.post(function (req, res) {
console.log(JSON.stringify(req.body));
return res.send("post returned " + JSON.stringify(req.body));
})
};
At the moment, I get the updates from the Service and print them out, but how do I update the view as well?
try use $watch() function.. when the value is modified, the view is updated.
$scope.$watch(function(scope) { return scope.data.myVar },
function(newValue, oldValue) {
document.getElementById("").innerHTML =
"" + newValue + "";
}
);
I dont know how you've constructed this project's structure, but if you're using a API, use http post to notify your front when changes are done.
the digest or watch, will make your controller do a function always the value has changed.
Angular Digest,Watch and Apply docs.
i hope this helps.
cya.

Persisting data between routes without going to the server?

I am working on a log in for my backbone application and came on an issue I am not sure how to solve without making a call to the server. This brought up a discussion in my team about what the way other folks are handing this kind of thing in backbone because we think we will be running into a similar thing moving forward.
It's Friday and I'm probably just brain dead, but here goes...
We have a User Model. The login method of the View creates a new user Model and call's it's login method passing in the user's credentials and a callback function which has an object that contains the users information.
Here is the login method for our View:
login: function(event){
event.preventDefault();
var user = new App.User;
user.login($('#username').val(), $('#password').val(),
(function(msg) {
// success callback
if (msg.loggedIn) {
console.log("Authenticate successful: " + JSON.stringify(msg));
var data = { user : msg, bob : "bob", trigger:true };
console.log("Prepared data: " + JSON.stringify(data));
App.router.navigate('home',data);
} else {
console.log("Authenticate unsuccessful: " + JSON.stringify(msg));
}
}).bind(this),
function(msg) {
// failure callback
console.log("Authenticate communication failure: " + JSON.stringify(msg));
alert("Communication fail!");
App.router.navigate('login',{trigger:true});
});
}
What we are trying to figure out is how to best make this Model data available to another route (home) so we can use it in the View.
So I have this router:
routes: {
'': 'home',
'home': 'home',
'login': 'login'
},
home: function(data){
console.log(data);
}
Once we have logged the user in we need to update the route and have access to that users data, but don't want to have to make a trip back to the server to fetch it.
I am concerned because we are building a kind of "wizard" where the user may need to move forward and backward through some steps and I don't want to have to hit the server every time they navigate through the application, but it's seeming like we are going to either need to save stuff to a global variable (don't want to do this) or make a trip back to the server every time. I'm sure others have had to deal with similar issues. Just looking for some insight.
Thanks!
"it's seeming like we are going to either need to save stuff to a global variable (don't want to do this)"
To state the obvious: You're going to need to preserve state. Your options are either to transfer the state to the server and back, or hold state on the client. Since you've already identified that you don't want to pass the state via a server, you're left with preserving state between different pages (routes) on the client.
And that's what global variables are for. It sounds icky, I know, but it's also one of the main benefits that Single-Page Applications bring to the table. Statefulness. And that state will always be held by some global object.
There are better and worse ways of managing state. Having a global variable called data that you keep assigning and reassigning is obviously the worst way. You should figure out a pattern that makes sense for your requirement.
If I understood your code sample correctly, what you want to store is the information about the current user. It also seems that you already have a global variable App. It occurs to me that it would be a good idea to keep some kind of session info:
login: function(data){
doLogin({
success: function(userData) {
App.session.currentUser = userData;
App.router.navigate('home', { trigger:true });
}
});
},
home: function(data){
if(!App.session || !App.session.currentUser) {
App.router.navigate('login', { trigger:true });
return;
}
var user = App.session.currentUser;
//do something with user
}
State is not necessarily evil. What's evil is depending on global state throughout the application, which leads easily to untestable spaghetti code. But if you resolve the state dependency as "high up" in the chain as possible (e.g. in the Router), and pass the values down using constructors and method arguments, you can still keep the testability and side-effectlessness in the rest of the codebase.
Sorry that I don't have a silver bullet for you. There are some libraries, Backbone.StateManager among them, which can help in managing state, transitions and such, but essentially they don't do anything you can't do for yourself.
Use localStorage!
Modify your code to do the following:
// success callback
if (msg.loggedIn) {
console.log("Authenticate successful: " + JSON.stringify(msg));
var data = { user : msg, bob : "bob", trigger:true };
var dataString = JSON.stringify(data);
console.log("Prepared data: " + dataString;
window.localStorage.setItem("userdata",dataString);
App.router.navigate('home',data);
Now whenever you need to check if the user is logged in, do the following:
try {
var userData = window.localStorage.getItem ("userdata");
} catch (e) {
// Do something
}
The try-catch is necessary to make sure that your code doesn't barf if the authentication has never been successful.
Its too late to reply but there is another much better way to do this depending on the router engine you are using and with no local variables.
I would try to give a general example using backbone which applies to all.
Generally your router would be in one place where things are being handled. Assuming its backbone lets have the routes defined as follows.
var router = Backbone.Router.extend({
routingData: {}, // this will have the routing data
routes: {
'': 'home',
'home': 'home',
'login': 'login'
},
navigate: function(url, data) {
this.routingData["data"] = data; // whenever navigation is done, this will be reset
//This is the routing code whichever the f/w may be.
Backbone.Router.prototype.navigate(route, { trigger: true });
},
home: function(data) {
var params = this.routingData["data"]; //retreiving the routing params
console.log(params);
}
})
Now if you want to pass data, you can do
router.navigate(<URL>,<SOME DATA>)

Meteor template reload infinity

I have a problem when running with Meteor.
I have a "question" page which I want to increase the count view whenever it is rendered.
So in my template function I write
Template.questionview.helpers({
question : function() {
if(Session.equals('main_template_name', 'question')) {
console.log(Session.get('question_id'));
Questions.update({
_id: Session.get('question_id')
}, {
$inc: {
views: 1
}
});
}
});
Now here comes the problem, when I render the question view and update the question item, the view is refreshed again because it is a reflective page. And then it comes infinity loop.
Anyone has suggestions?
Typically, in situations like this, there is something broken about the model. In this case, I believe it's the "count view" idea. There are lots of ways to do this correctly. Incrementing it on render is not, since you're doing model work in UI code (broken conceptually and in implementation).
First, store the questions the user has visited somewhere. Why not a {questionsVisited:[]} property on the user?
Use a Meteor.call(...) method call to register a view instead:
Meteor.methods({
viewQuestion: function(questionId) {
// check if the user hasn't visited this question already
var user = Meteor.users.findOne({_id:this.userId,questionsVisited:{$ne:questionId}});
if (!user)
return false;
// otherwise, increment the question view count and add the question to the user's visited page
Meteor.users.update({_id:this.userId},{$addToSet:{questionsVisited:questionId}});
Questions.update({_id:questionId},{$inc:{views:1}});
return true;
});
So how about incrementing the view on UI changes? Well, let's not do that specifically. Let's increment the view count only when the question changes.
Meteor.autorun(function () {
var questionId = Session.get("question_id");
Meteor.call('viewQuestion',questionId,function(e,r) {
if (r)
console.log("Question " + questionId + " logged an increment.");
else
console.log("Question " + questionId + " has already been visited by user " + Meteor.userId();
});
});
And get rid of all this question helper stuff...
This is even better than what you originally wanted. Now views aren't counted twice for the same user. If that is the desired behavior, remove the questionsVisited logic.
Only change the 'question_id' session variable when you actually change the logical question the user is working with.
I solved this problem by using meteor-collection-hooks
First install it
>_ meteor add matb33:collection-hooks
Then in your model
Questions.after.findOne(function (userId, selector, options, doc){
Questions.update({_id: doc._id},{$inc:{views:1}});
});
And KABOOM Thats it
Rather than put in a helper, I would put this logic into the rendered event, ie.
Template.questionview.rendered ...
See the Meteor docs.
If you are using publish and subscribe ( you definitely should anyway) you can do it on your publish method like
Meteor.publish('posts', function(id) {
Posts.update({_id:id},{$inc:{view:1}});
return Posts.find({_id: id});
});
or on your subscribe callback
Meteor.subscribe("posts" , id {
onReady: function() { Meteor.call("incrementView", id);}});
this way you only increment the count once each time users open the browser.

Callbacks in views

This may be more of a question around design pattern - I hope it makes sense.
I am using backbone - am developing a relatively simple app where user can add requests (where the request model is simply heading, description and price). The only requirement to add the requests is that a user is 'logged in'.
However I wish that the user 'adds' the request before checking if the user is logged in (remove one barrier). By that I mean that the user fills the form in, if not registered they have to register and then the request is just sent, if they were logged in it would just be sent. So initially the 'add request' view is rendered, when save is triggered this will call the save on the model which calls the ajax request on the server. The response will either return true (the user was logged in and the request added) or false (the user was not logged in).
So assuming that the user was not logged in - then I would wish to render a new view 'register' which has the option for the user to switch to 'login' (another view). So User in theory could go from
Request (save) -> Register -> Login -> Request (save)
So presuming that the user then registered (filled in the form which then called the registers view save method which then called the registers model save and returned ok). I would then wish to once again call the 'request' save method once again (as now the user would be logged in). However I do not want the register/login tied to the Request view - as in theory a new view (lets say I had a sent message view) would want similar functionality e.g. try and make the request, if failed switch view to register - perform save and then call some callback.
So the question is what is the right way to do this?
1) Do I create some closure inside the request view referencing the 'save' function and store it in a global to be called by register/login onsuccess?
2) Create a closure as above and pass that to the register etc (and if so how would I pass this given register/login is a view).
3) Pass a reference to 'this' of the request view?
So far all I have is, so in the else I would render the 'register' view but would love to know best way to do this without it getting very ugly?
save : function(event){
if(this.model.isValid() == true) {
this.$("#general_error").hide();
this.model.set({'formattedPrice' : TB_H.formatPrice(this.model.get('currency'), this.model.get('price'))});
self = this;
this.model.save(this.model.toJSON(), {
success: function(m, y) {
if(y.status == true) {
self.model = new TB_BB.Request();
Backbone.ModelBinding.bind(self);
Backbone.Validation.bind(self);
$('#add-offer-button').show();
} else {
if(y.errors[0] == 'not logged in') {
this.$("#general_error").html('You are not logged in');
this.$("#general_error").show();
} else {
_.each(y.errors, function(key, val) { this.$("#general_error").html(key) });
this.$("#general_error").show();
}
}
}, error : function(m,y) {
this.$("#general_error").show();
this.$("#general_error").html("Something bad happened - please try again")
}
}
);
}
Greatly appreciate any help!
I noticed this after asking a similar question, which I just deleted. I'm thinking this isn't the way most people are doing it in backbone.
what I did instead was pass my different routes to the same method and if the ids were not null, I'd call the route.
So I have a view
base_view = Backbone.Views.extend({
initialize: function(id,a_id,b_id){
this.id = id;
this.a_id = a_id;
this.b_id = b_id;
Myapp.data = new Myapp.Model.Base();
Myapp.data.url = '/data_url/'+id;
Myapp.data.fetch(Myapp.data, {
success: function(response){
// i have some nested collections, and models so i fill those in here
Myapp.mainModel = new First_Child_Collection(response.attributes.first_child_array);
}, error: function(){
alert('oops couldn't get data');
}
});
Myapp.data.bind("fetched",this.render,this);
},
render: function(){
new Main_View();
new Sub_View_1(this.id);
new Sub_View_2(this.a_id);
new Sub_View_3(this.b_id);
}
});
Then in my routes, rather than having a new route for each, I have
routes: {
"app/new": "new",
"app/:id/edit/a/:a_id/b/:b_id": "edit"
}
edit {
new base_view(id,a_id,b_id);
}
I'm not sure if this is perfect, but I think it is DRY'r than the other options. I just check that a_id or b_id are not undefined before passing those views.
Hope it helps.

Resources