Ember: Using async data in controller - asynchronous

I have an asynchronous call in an Ember controller, #authSession.currentUser.clients. The results of this request is then filtered to populate a drop down menu.
filterClientsBy: (term) ->
filter = new RegExp("^#{term}", 'i')
#authSession.currentUser.clients.filter (client) ->
filter.test(client.displayName)
The problem is that the filter doesn't work the first time because it doesn't wait for clients to come back from the server. I feel like I'm missing something fundamental about Ember here. It looks like clients is a ModelArray. Can I treat this like a promise somehow? Is there a way to bootstrap this data into the controller before rendering my template?

Ok, so there is a couple of things you could do here.
Because there is an asynchronous call in there you need to use a promise in order to wait for the clients relationship to come back. (This is assuming you have "async" set on your client relationship in your model)
This leaves you with a couple of options (I did these in javascript and converted them to coffeescript, so it may look a bit strange):
1) return a Promise from "filterClientsBy" like so:
filterClientsBy: (term) ->
self = this
filter = new RegExp("^" + term, "i")
new Ember.RSVP.Promise((resolve, reject) ->
self.authSession.currentUser.clients.then (clients) ->
filteredList = clients.filter((client) ->
filter.test client.displayName
)
resolve filteredList
return
return
)
2) You could create a property on your controller called "filteredClients" that you set inside the resolved "clients" promise block above (instead of returning the promise).
filterClientsBy: (term) ->
self = this
filter = new RegExp("^" + term, "i")
self.authSession.currentUser.clients.then (clients) ->
filteredList = clients.filter((client) ->
filter.test client.displayName
)
self.set "filteredClients", filteredList
return
return

Related

Meteor with Angular2 , Fetching all entries from a collection in single shot

I have successfully integeraed meteor with angular2 but while fetching the data from collection facing difficulties in getting at one shot, here is the steps:
Collection Name : OrderDetails
No Of records : 1000
Server:
Created publication file to subcribe the collection:
Meteor.publish('orderFilter', function() {
return OrderLineDetails.find({});
});
Client:
this.dateSubscription =
MeteorObservable.subscribe('orderFilter').subscribe(()=> {
let lines = OrderDetails.find({expectedShipDate:{$in:strArr}},{fields:
{"expectedShipDate":1,"loadNo":1},sort:{"expectedShipDate":1}}).fetch();
});
In this lines attribute fetches all the collection entries, but fails to subscribe for the changes
When I try with below one,
OrderDetails.find({expectedShipDate:{$in:strArr}},{fields:{"expectedShipDate":1,"loadNo":1},sort:{"expectedShipDate":1}}).zone().subscribe(results => {
// code to loop the results
});
In this am able to subscribe for the collection changes, but the results are looped for 1000 times , as 1000 entries in the colleciton.
Is there any way to get the whole collection entries in one single shot and mean time to subscribe the changes in the collection ?.
Yes, there are a couple of ways you can do it, mostly depending on how you want to handle the data.
If having everything at once is important, then use a Method such as:
MeteorObservable.call('getAllElements', (err, result) => {
// result.length === all elements
})
While on server side doing
Meteor.methods({
getAllElements:function(){return myCollection.find().fetch()}
})
Now, if you want to listen to changes, ofcourse you'll have to do a subscription, and if you want to lower the amount of subscriptions, use rxjs' debounceTime() function, such as (from your code):
this.theData.debounceTime(400).subscribe(value => ...., err =>)
This will wait a certain amount of time before subscribing to that collection.
Now, based on your intent: listening to changes and getting everything at once, you can combine both approaches, not the most efficient but can be effective.
As #Rager explained, observables are close to streams, so when you populate data on miniMongo (front end collection you use when you find() data and is populated when you subscribe to publications) it will start incrementing until the collection is in sync.
Since miniMongo is populated when you subscribe to a publication, and not when you query a cursor, you could either:
Try the debouceTime() approach
Use a Meteor.Method after subscribing to the publication, then sync both results, keeping the first response from the method as your starting point, and then using data from Collection.find().subscribe(collectionArray => ..., err=>) to do whatterver you want to do when changes apply (not that recommended, unless you have a specific use case for this)
Also, .zone() function is specific to force re-render on Angular's event cycle. I'd recomend not use it if you're processing the collections' data instead of rendering it on a ngFor* loop. And if you're using an ngFor* loop, use the async pipe instead ngFor="let entry of Collection | async"
I don't think that's possible. When you subscribe to an Observable it handles values as a "stream", not necessarily a loop. I have seen some makeshift helper methods that handle the data synchronously, though the time it takes to subscribe is not decreased. Check out this article for an under the hood look... A simple Observable implementation
However, you can set it up to only loop once.
The way that I've been setting up that scenario, the collection only gets looped through one time (in the constructor when the app starts) and detects changes in the collection. In your case it would look like:
values: YourModel[] = []; //this is an array of models to store the data
theData: Observable<YourModel[]>;
errors: string[];
subFinished: boolean = false;
constructor(){
this.theData = OrderDetails.find({expectedShipDate:{$in:strArr}},{fields:{"expectedShipDate":1,"loadNo":1},sort:{"expectedShipDate":1}}).zone();
MeteorObservable.subscribe('orderFilter').subscribe();
//push data onto the values array
this.theData.subscribe(
value => this.values = value,
error => this.errors.push("new error"),
() => this.subFinished = true
);
}
The "values" array is updated with whatever changes happen to the database.

meteor how to define class level methods?

I have a collection called "Articles". Each article has a category. I would like to have a global variable being an array with each distinct category value in my Articles collection.
I tried to do it this way:
/models/article.coffee:
#Articles = new Meteor.Collection "articles"
Articles.categories = ->
Meteor.call "articleCategories", (e, r) ->
unless e
return r
/server/article_server.coffee:
Meteor.methods
articleCategories: ->
categories = _.uniq(Articles.find({}, {sort: {category: 1}, fields:
{category: true}}).fetch().map (x) ->
x.category
, true)
return categories
This doesn't work. The result is "undefined" when I call Articles.categories() from the console.
What am I doing wrong?
EDIT:
I want to do this because I want my article categories to be available everywhere in the website.
As Articles collection will not be published on every pages, I tought, I could just generate an array server side and pass it over to the client.
But maybe it's not a good idea...
A Meteor.method will always return undefined on the client (unless a simulation/stub exists and it's called within another parent method) so this behavior is expected.
I'm not sure why you'd need a Meteor.method in this particular use case though, can't you just copy your method code inside your class method ?
EDIT :
To accomplish what you want to do, I'd suggest changing your model to create a Categories collection filled with every possible categories and just publish the entire content to the client.
Then just use a foreign key in your Articles collection.
An added benefit will be that your categories access client side will be reactive, contrary to using a Meteor.method.
Whether it's Telescope or even Wordpress I think this schema is very popular.
Take a look at this package:
https://github.com/dburles/meteor-collection-helpers
And add something like this (I wrote in javascript) in your model:
Articles.helpers({
categories: function(){
return _.uniq(
_.pluck(Articles.find({}, {sort: {category: 1}, fields:
{category: true}}).fetch(), 'category'),
true
);
}
});

Meteor: Get count of collection by name. Accessing global scope on server

I'd like to create a method that returns the count of a generic collection.
Calling the method would look something like this:
Meteor.call('getCollectionCount', 'COLLECTION_NAME');
And the result would be the collection count.
The server method code would look something like this:
getCollectionCount: function (collectionName) {
return window[collectionName].find().count();
}
This won't work because window isn't defined on the server, but is something similar possible?
Use global instead of window.
Note that this uses the variable name assigned to the collection object, not the name given to the collection. For this to work with Meteor.users you need to assign another variable name.
if (Meteor.isServer) {
users = Meteor.users;
}
if (Meteor.isClient) {
Meteor.call('count', 'users', function (err, res) {
// do something with number of users
});
}
Also probably a good idea to check that global[collectionName] is actually a collection.
I came up with this code which makes the following assumptions :
collections are declared in the global scope as top level objects.
collections are searched by collection name, not the collection variable identifier.
So client code should declare their collections like this :
MyCollection=new Meteor.Collection("my-collection");
And use the function like this :
var clientResult=Meteor.call("getCollectionCount","my-collection",function(error,result){
if(error){
console.log(error);
return;
}
console.log("actual server-side count is : ",result);
});
console.log("published subset count is : ",clientResult);
The method supports execution on the client (this is known as method stub or method simulation) but will only yield the count of the collection subset replicated client-side, to get the real count wait for server-side response using a callback.
/packages/my-package/lib/my-package.js
getCollection=function(collectionName){
if(collectionName=="users"){
return Meteor.users;
}
var globalScope=Meteor.isClient?window:global;
for(var property in globalScope){
var object=globalScope[property];
if(object instanceof Meteor.Collection && object._name==collectionName){
return object;
}
}
throw Meteor.Error(500,"No collection named "+collectionName);
};
Meteor.methods({
getCollectionCount:function(collectionName){
return getCollection(collectionName).find().count();
}
});
As Meteor.users is not declared as a top level variable you have to account for the special case (yes, this is ugly).
Digging into Meteor's collection handling code could provide a better alternative (getting access to a collection handle by collection name).
Final words on this : using a method call to count a collection documents is unfortunately non-reactive, so given the Meteor paradigm this might be of little use.
Most of the time you will want to fetch the number of documents in a collection for pagination purpose (something like a "Load more" button in a posts list for example), and as the rest of the Meteor architecture you'll want this to be reactive.
To count documents in a collection reactively you'll have to setup a slightly more complicated publication as showcased in the "counts-by-room" example in the docs.
http://docs.meteor.com/#meteor_publish
This is something you definitely want to read and understand.
This smart package is actually doing it right :
http://atmospherejs.com/package/publish-counts
It provides a helper function that is publishing the counts of any cursor.
Keep track of the collections on some other property that the server has access too. You could even call it window if you really wanted to.
var wow = new Meteor.Collection("wow");
collections["wow"] = wow;
getCollectionCount: function (collectionName) {
return collections[collectionName].find().count();
}
If you don't want the package users to change how they work with collections in the app then I think you should use MongoInternals to get collections by name from the db. Not tested but here is an example:
//on server
Meteor.methods({
count: function( name ){
var db = MongoInternals.defaultRemoteCollectionDriver().mongo.db;
var collection = db.collection( name );
return collection && collection.count({});
}
});
Another example of MongoInternals use is here. Documentation of the count() function available from the mongo driver is here.

How to manage and selectively stop a published subscription from Meteor server side code

I'm developing a competitive turn based game. When an anonymous visitor visits a page, he is automatically subscribing to a non-full instance of the game he can then either just observe or join the action. There are limited number of spots in each game instance. When joining the game (taking up a spot), the old subscription is stopped and new one is created that also includes private information based on his chosen spot. So far so good.
Now I want to make the server free up a spot whenever a player doesn't complete his turn in reasonable amount of time.
The question is how can I make sure the player kicked out of his spot no longer receives updates that are now intended to go to someone else occupying his spot? Obviously this has to happen on server side as clients cannot be trusted. Ideally the kicked out user would seamlessly become the observer again.
I know there is a method stop() that can be called inside publish(), but how to use it to stop a published subscription for one particular client when a callback set by Meteor.setTimeout() gets called on the server?
Some heavily modified code of what I'm trying to do (not meant to work, only to give you an idea)
if Meteor.isClient
publicGameHandle = Meteor.subscribe 'GameInstances'
join = (gameInstanceId, spot) ->
Meteor.call "join", gameInstanceId, spot, (err, guestId) ->
Session.set("guestId", guestId)
privateGameHandle = Meteor.subscribe 'GameInstances',
gameInstanceId, spot, guestId, ->
publicGameHandle.stop()
if Meteor.isServer
privateSubscriptions = {}
Meteor.publish 'GameInstances', (gameInstanceId, spot, guestId) ->
if gameInstanceId
GameInstances.find {_id: gameInstanceId}
privateSubscriptions[guestId] = #
else
secretFields = {spots.guestId:false, spots.privateGameInfo:false}
GameInstances.find {openSpots: {$gt: 0}}, {fields: secretFields}
Meteor.methods({
join: (gameInstanceId, spot) ->
guestId = Random.id()
gameInstances[gameInstanceId].addPlayer(spot, guestId)
guestId
completePlayerTurn: (gameInstanceId, spot, guestId) ->
gameInstance = gameInstances[gameInstanceId]
Meteor.clearTimeout(gameInstance.timer)
nextPlayer = gameInstance.getNextPlayer()
kick = () ->
privateSubcriptions[nextPlayer.guestId].stop()
gameInstance.removePlayer(nextPlayer.guestId)
gameinstance.timer = Meteor.setTimeout(kick, 60000)
Create two separate subscriptions that both return game data. One subscription should return public game data, the other should return game data that is viewable by the currentUser. They will be merged into one collection on the client side. Games the user has joined will contain private info AND public info, games they haven't joined will only contain public info. Then on the client side, a liveQuery like Game.findOne(thisGame.id) will automatically receive the private information when the player joins the game and cause your template to be re-rendered, etc.
Pseudo-code for illustrative purposes:
if Meteor.isClient
Meteor.subscribe 'GameInstances'
Meteor.subscribe 'MyGameInstances', guestId
join = (gameInstanceId, spot) ->
Meteor.call "join", gameInstanceId, spot, (err, guestId) ->
Session.set("guestId", guestId)
if Meteor.isServer
Meteor.publish 'GameInstances', () ->
secretFields = {spots.guestId:false, spots.privateGameInfo:false}
GameInstances.find {openSpots: {$gt: 0}}, {fields: secretFields}
Meteor.publish 'MyGameInstances', (guestId) ->
GameInstances.find {'spots.guestId': guestId}, {fields: secretFields}
Meteor.methods({
join: (gameInstanceId, spot) ->
guestId = Random.id()
gameInstances[gameInstanceId].addPlayer(spot, guestId)
guestId
completePlayerTurn: (gameInstanceId, spot, guestId) ->
gameInstance = gameInstances[gameInstanceId]
Meteor.clearTimeout(gameInstance.timer)
nextPlayer = gameInstance.getNextPlayer()
kick = () ->
gameInstance.removePlayer(nextPlayer.guestId)
gameinstance.timer = Meteor.setTimeout(kick, 60000)
It sounds like you might be including many players private info on one Game Instance. So that will be harder to filter the fields that are allowed (and only publish the CURRENT guest's private info, without publishing other players' private info).
In that case, you might consider either:
refactor private info into a separate collection, and publish it as a separate subscription
custom publish function. Here's an example:
https://gist.github.com/colllin/8321227
In this code, I'm observing one collection and publishing data that will be merged into the Meteor.users collection. You can see that the subscription is publishing data like {_id: userId, wins: 25}, which makes a wins field available on user models on the client side. You will probably want to observe() the Games collection but manually filter the private data based on the current user, then publish that private data into the Games collection to be merged with the public data. You can pass in the guestId to the publish function like you did above.

Angularjs multiple $http.get request

I need to do two $http.get call and I need to send returned response data to my service for doing further calculation.
I want to do something like below:
function productCalculationCtrl($scope, $http, MyService){
$scope.calculate = function(query){
$http.get('FIRSTRESTURL', {cache: false}).success(function(data){
$scope.product_list_1 = data;
});
$http.get('SECONDRESTURL', {'cache': false}).success(function(data){
$scope.product_list_2 = data;
});
$scope.results = MyService.doCalculation($scope.product_list_1, $scope.product_list_2);
}
}
In my markup I am calling it like
<button class="btn" ng-click="calculate(query)">Calculate</button>
As $http.get is asynchronous, I am not getting the data when passing in doCalculation method.
Any idea how can I implement multiple $http.get request and work like above implementation to pass both the response data into service?
What you need is $q.all.
Add $q to controller's dependencies, then try:
$scope.product_list_1 = $http.get('FIRSTRESTURL', {cache: false});
$scope.product_list_2 = $http.get('SECONDRESTURL', {'cache': false});
$q.all([$scope.product_list_1, $scope.product_list_2]).then(function(values) {
$scope.results = MyService.doCalculation(values[0], values[1]);
});
There's a simple and hacky way: Call the calculation in both callbacks. The first invocation (whichever comes first) sees incomplete data. It should do nothing but quickly exit. The second invocation sees both product lists and does the job.
I had a similar problem recently so I'm going to post my answer also:
In your case you only have two calculations and it seems to be the case this number is not mutable.
But hey, this could be any case with two or more requests being triggered at once.
So, considering two or more cases, this is how I would implement:
var requests = [];
requests.push($http.get('FIRSTRESTURL', {'cache': false}));
requests.push($http.get('SECONDRESTURL', {'cache': false}));
$q.all(requests).then(function (responses) {
var values = [];
for (var x in responses) {
responses[x].success(function(data){
values.push(data);
});
}
$scope.results = MyService.doCalculation(values);
});
Which, in this case, would force doCalculation to accept an array instead.

Resources