React and Meteor Subscription - reactjs

I am not sure if this is a limitation to React and Meteors connection as documentation suggests that it should be possible without the extra parameter.
When I call a meteor subscription in react if I do not explicitly state the parameter in the query it returns any data, ignoring the specified data in the publish function.
Meteor.publish("supplier", function() {
if(this.userId) {
var user = Meteor.users.findOne(this.userId, { fields : { active : 1 }});
if(user.active != this.userId || user.active != undefined){
// This only returns 1 singular supplier - is correct
var supplier = Supplier.find({ _id : user.active, users : this.userId });
return supplier;
} else {
return this.ready();
}
} else {
return this.ready();
}
});
Now I call the subscription in react as so
getMeteorData: function () {
var data = {}
handle = Meteor.subscribe("supplier");
if(handle.ready()) {
data.supplier = Supplier.findOne(); // Returns Wrong supplier
//data.supplier = Supplier.findOne({_id: session.get("active")}) // Returns correct supplier
data.supplierReady = true
}
return data;
},
This returns the first supplier in the collection not the one logged in the publish function on the server! However if I explicably pass { _id : user.active} it works!
Now it was my understanding that by doing the logic on the server within the publish function that I could simply use Supplier.findOne() but this is not the case and I don't understand why. Is this a limitation on React/Meteor or am I implementing this wrong?

This isn't a React-specific issue, it's a result of the way findOne works. If you have one or more documents in your client side Supplier collection, Supplier.findOne() will just grab the first record available without reference to the document(s) you just fetched from your subscription.
This means either (a) you have more than one supplier available on the client side due to other preexisting subscriptions, or (b) you are returning more than one supplier from the handle subscription.
Check the state of the client side collection prior to the handle subscription. If there's 1 or more docs and that is the intended state of your application, then modify the client side findOne to add {_id: user.active} as you have before.

Related

Angular JS Select option from database not working

I have a select option list in Angular.js. I populate list from sql server database. It's working fine. But when I want to display data back from sql server, it doesn't work.
to populate from database (its populated)
<select ng-model="roomno" ng-options="r.Room_Name for r in rooms" value= "{{roomno}}"></select>
Now I want to show 1 particular item in that list, which is stored in database.
I tried with
$scope.roomno = response.data[0].Room_Name;
It's not working.
I would like to suggest You to use code like this:
$scope.roomno = response.data[0].Room_Name.toString();
this works fine. because somewhere stores as integer and somewhere string so need to convert as string.
How are you connecting to the server? Presumably you are using HTTP get? In which case if you want one particular room then you might want to call a separate request using the id?:
var _getRoom = function (roomId) {
return $http.get('api/Rooms/' + roomId).then(function (response) {
if (typeof response.data === 'object') {
_room = response.data;
return response.data;
} else {
return $q.reject(response.data);
}
}, function (response) {
return $q.reject(response.data);
});
};

Multiple resources in one service

In my MEAN application, I have a product model like this :
product: {
field: //some string value//,
reviews: //an array of review objects//
}
Now I am going to apply different restrictions for saving field and reviews, so my API endpoints will be like this :
/* specific route for updating reviews */
router.put('/products/reviews/:id', checkRightsToUpdateReviews, updateProductReviews);
/* specific route for updating field */
router.put('/products/:id', checkRightsToUpdateField, updateProductField);
Those endpoints are reached by my Angular service productData, respectively via methods productData.updateReview(product) and productData.updateField(product).
Thus in my productData service, I'm using two resources :
One with url /products/reviews/:id for the updateReview method, and one with url /products/:id for the updateField method.
I feel like I'm not doing the separation of concerns properly.
The problem originates from the fact that my model has fields that have to be treated differently. But it makes sense to me to have all the CRUD operations for products in one service.
What would be an obviously more elegant solution for this?
I'd use one authorization middleware to check for both cases.
router.put('/products/:id', checkUpdateRights, updateProduct);
function checkUpdateRights(req, res, next){
if(req.body.reviews && !hasReviewRights(req)){ // check only if reviews exists in body
res.status(401).send({ error: "Unauthorized to update product reviews" });
}
if(!hasUpdateRights(req)){ // for other fields
res.status(401).send({ error: "Unauthorized to update product" });
}
else{
return next();
}
};
function hasReviewRights(req){
// return true or false
};
function hasUpdateRights(req){
// return true or false
};

How to update user field in angular-meteor?

I've configured all users to be created with an empty favorites array: user.favorites: []
Since the users collection is treated differently, how should I publish, subscribe, and access subscribed favorites data in angular-meteor?
Here's what I have so far:
// Meteor.methods ==========================================
addFavorite: function(attendeeId){
var loggedInUser = Meteor.user();
if( !loggedInUser ){
throw new Meteor.Error("must be logged in");
}
loggedInUser.favorites.push(attendeeId);
loggedInUser.username = loggedInUser.username+"x";
console.log(loggedInUser.favorites);
}
// controller ========================================
$scope.addFavorite = function(attendeeId){
$meteor.call("addFavorite", attendeeId);
}
// server =======================================================
Meteor.publish('myFavorites', function(){
if(!this.userId) return null;
return Meteor.users.find(this.userId);
});
Meteor.users.allow({
insert: function(userId, doc){
return true;
},
update: function(useId, doc, fieldNames, modifier){
return true;
},
remove: function(userId, doc){
return true;
}
});
User.favorites is empty. When addFavorite is called, it logs an array with a single userId that doesn't update the mongoDB at all. It looks as if Meteor.user() isn't reactivly updating. Does anyone know what I'm doing wrong? Thank you!
EDIT
Latest iteration of code. Favorites are passed into $scope.favorites but isn't reactive. How do I fix this? Thanks!
// publish
Meteor.publish('myFavorites', function(){
if(this.userId){
return Meteor.users.find(this.userId, {
fields: {
favorites: 1
}
});
}else{
this.ready();
}
});
// subscribe
$meteor.subscribe('myFavorites')
.then(function(subscriptionHandle)
{
var user = $meteor.collection(function(){
return Meteor.users.find({_id: Meteor.userId()});
});
$scope.favorites = user[0].favorites;
});
tldr;
Accounts collection is reactive, but by default only the username, emails, and profile fields are published. The quickest fix is to attach the favorites as a new field on the User.profile object.
// Meteor.methods ==========================================
addFavorite: function(attendeeId){
var loggedInUser = Meteor.user();
if( !loggedInUser ){
throw new Meteor.Error("must be logged in");
}
if (loggedInUser.profile.favorites){
loggedInUser.profile.favorites.push(attendeeId);
}
else {
loggedInUser.profile.favorites = [];
loggedInUser.profile.favorites.push(attendeeId);
}
loggedInUser.username = loggedInUser.username+"x";
console.log(loggedInUser.profile.favorites);
}
Although right now you probably are writing to the user, which you can verify by using meteor mongo --> db.users.find().pretty(), but the subscription does not publish your favorites field.
Alternative approach
Alternatively, you can publish the favorites field
// Server code --------
Meteor.publish("userData", function () {
if (this.userId) {
return Meteor.users.find({_id: this.userId},
{fields: {'favorites': 1}});
} else {
this.ready();
}
});
Opinionated Meteor.users philosophy
I like to structure my users object around 3 properties:
User.profile --> published to the client, and directly modifiable by the client through client-side code
User.public --> published to the client, but not modifiable except through server-side Meteor methods
User.private --> not published to the client (i.e. only accessible to read on server code), and only modifiable by server code (with client simulation)
Just make sure that when you remove the insecure and autopublish packages that you double-check your Collections security by using the Meteor.users.allow() function in your server code
Run meteor list to if you want to verify whether or not insecure and autopublish packages are being used in your current project. NOTE: By default Meteor does install them when you first create your app)
// Server code --------
Meteor.publish("userData", function () {
if (this.userId) {
return Meteor.users.find({_id: this.userId},
{fields: {'public': 1}});
} else {
this.ready();
}
});

Connection state with doowb/angular-pusher

I am trying to build an Angular project with Pusher using the angular-pusher wrapper. It's working well but I need to detect when the user loses internet briefly so that they can retrieve missed changes to data from my server.
It looks like the way to handle this is to reload the data on Pusher.connection.state('connected'...) but this does not seem to work with angular-pusher - I am receiving "Pusher.connection" is undefined.
Here is my code:
angular.module('respondersapp', ['doowb.angular-pusher']).
config(['PusherServiceProvider',
function(PusherServiceProvider) {
PusherServiceProvider
.setToken('Foooooooo')
.setOptions({});
}
]);
var ResponderController = function($scope, $http, Pusher) {
$scope.responders = [];
Pusher.subscribe('responders', 'status', function (item) {
// an item was updated. find it in our list and update it.
var found = false;
for (var i = 0; i < $scope.responders.length; i++) {
if ($scope.responders[i].id === item.id) {
found = true;
$scope.responders[i] = item;
break;
}
}
if (!found) {
$scope.responders.push(item);
}
});
Pusher.subscribe('responders', 'unavail', function(item) {
$scope.responders.splice($scope.responders.indexOf(item), 1);
});
var retrieveResponders = function () {
// get a list of responders from the api located at '/api/responders'
console.log('getting responders');
$http.get('/app/dashboard/avail-responders')
.success(function (responders) {
$scope.responders = responders;
});
};
$scope.updateItem = function (item) {
console.log('updating item');
$http.post('/api/responders', item);
};
// load the responders
retrieveResponders();
};
Under this setup how would I go about monitoring connection state? I'm basically trying to replicate the Firebase "catch up" functionality for spotty connections, Firebase was not working overall for me, too confusing trying to manage multiple data sets (not looking to replace back-end at all).
Thanks!
It looks like the Pusher dependency only exposes subscribe and unsubscribe. See:
https://github.com/doowb/angular-pusher/blob/gh-pages/angular-pusher.js#L86
However, if you access the PusherService you get access to the Pusher instance (the one provided by the Pusher JS library) using PusherService.then. See:
https://github.com/doowb/angular-pusher/blob/gh-pages/angular-pusher.js#L91
I'm not sure why the PusherService provides a level of abstraction and why it doesn't just return the pusher instance. It's probably so that it can add some of the Angular specific functionality ($rootScope.$broadcast and $rootScope.$digest).
Maybe you can set the PusherService as a dependency and access the pusher instance using the following?
PusherService.then(function (pusher) {
var state = pusher.connection.state;
});
To clarify #leggetters answer, you might do something like:
app.controller("MyController", function(PusherService) {
PusherService.then(function(pusher) {
pusher.connection.bind("state_change", function(states) {
console.log("Pusher's state changed from %o to %o", states.previous, states.current);
});
});
});
Also note that pusher-js (which angular-pusher uses) has activityTimeout and pongTimeout configuration to tweak the connection state detection.
From my limited experiments, connection states can't be relied on. With the default values, you can go offline for many seconds and then back online without them being any the wiser.
Even if you lower the configuration values, someone could probably drop offline for just a millisecond and miss a message if they're unlucky.

Angular $resource and use of Interceptor to check response/error/callback

Last few days I am working to invoke REST services and track the response, error, callback etc. I have gone through most of the posting however due to my limited understanding on Angular seems like I am not able to understand it. Following is my problem and understanding I got so far.
I am using Project.$update() service which returns only "project_id". This server doesn't return complete data again. Following is line of few line of code to share here.
//create Project factory
app.factory('Project', function ($resource) {
return $resource('/api/projects/:projectid',
{projectid:'#id'},
{update: {method:'PUT', isArray:false}}
);
});
Following is code in directive I am using to update/create project.
//save project
scope.saveProject = function (project) {
//update modified by field
project.modifiedby = scope.user._id;
//change to view mode
scope.projectView = 1;
//call server to save the data
if (project._id == undefined || project._id == "") {
//Call server to create new and update projectID
project._id = project.$save()._id;
}
else {
//Call server to update the project data
project.$update({ projectid: project._id });
}
};
Following is service response for both save() and update().
{"_id":"52223481e4b0c4d1a050c25e"}
Problem here is; "project" object value is replaced by new response returned by server having only project_id and other fields are replaced.
I was going through detailed documentation on $resource however I am not able to grasp it. It will be great to get some guidance here to write code to detect error, response, callback.
You can replace the original object by the one returned from the server in your success callback like this:
//save project
scope.saveProject = function (project) {
//update modified by field
project.modifiedby = scope.user._id;
//change to view mode
scope.projectView = 1;
//call server to save the data
if (project._id == undefined || project._id == "") {
//Call server to create new and update projectID
project.$save(function(updatedProject, headers){
// Replace project by project returned by server
project = updatedProject;
});
}
else {
//Call server to update the project data
project.$update(function(updatedProject, headers){
// Replace project by project returned by server
project = updatedProject;
});
}
};
That will replace the original object by the one returned by the server as soon as the server response is received.
If your callback is identical for the $save and $update methods, you can further simplify your code like this:
//save project
scope.saveProject = function (project) {
//update modified by field
project.modifiedby = scope.user._id;
//change to view mode
scope.projectView = 1;
var action = (angular.isDefined(project._id)) ? '$update' : '$save';
//call server to save the data
project[action](function(updatedProject, headers){
// Replace project by project returned by server
project = updatedProject;
});
};
Hope that helps!
As per suggestion made by jvandemo and BoxerBucks; I have used following approach for save/update by passing the callback method with copy of original data. However still I am looking for central approach to take care of error/success status. Please suggest.
//save project metadta
scope.saveProjectMetadta = function (project) {
//update modified by field
project.modifiedby = scope.user._id;
//change to view mode
scope.projectView = 1;
//keep original data to pass into callback
var originalProjectObject = angular.copy(project);
//call server to save the data
if (project._id == undefined || project._id == "") {
//Call server to create new and update projectID
project.$save(originalProjectObject, function (projectResponse) {
originalProjectObject._id = projectResponse._id;
//update scope
scope.project = originalProjectObject;
//invoke method to update controller project object state
scope.updateProjectScope(scope.project);
});
}
else {
//Call server to update the project data
project.$update({ projectid: project._id }, function (projectResponse) {
originalProjectObject._id = projectResponse._id;
//update scope
scope.project = originalProjectObject;
//invoke method to update controller project object state
scope.updateProjectScope(scope.project);
},originalProjectObject);
}
};

Resources