J'ai mis en place une application simple qui affiche une liste de Projects
. J'ai supprimé le package autopublish
afin de ne pas tout envoyer au client.
<template name="projectsIndex">
{{#each projects}}
{{name}}
{{/each}}
</template>
Lorsque autopublish
était activé, cela afficherait tous les projets:
if Meteor.isClient
Template.projectsIndex.projects = Projects.find()
Une fois retiré, je dois en plus:
if Meteor.isServer
Meteor.publish "projects", ->
Projects.find()
if Meteor.isClient
Meteor.subscribe "projects"
Template.projectsIndex.projects = Projects.find()
Alors, est-il exact de dire que la méthode find()
côté client ne recherche que les enregistrements qui ont été publiés depuis le côté serveur? Cela m'a fait trébucher parce que je sentais que je ne devrais appeler find()
qu'une seule fois.
Les collections, les publications et les abonnements sont un domaine délicat de Meteor, que la documentation pourrait discuter plus en détail, afin d'éviter fréquentconfusion , qui sont parfois amplifiés par terminologie confuse .
Voici Sacha Greif (co-auteur de DiscoverMeteor ) expliquant les publications et les abonnements dans une diapositive:
Pour bien comprendre pourquoi vous devez appeler find()
plusieurs fois, vous devez comprendre le fonctionnement des collections, des publications et des abonnements dans Meteor:
Vous définissez des collections dans MongoDB. Aucun Meteor impliqué pour le moment. Ces collections contiennent enregistrements de base de données (également appelés "documents" par Mongo et Meteor , mais un "document" est plus général qu'un enregistrement de base de données; par exemple, une spécification de mise à jour ou un sélecteur de requête sont des documents trop - objets JavaScript contenant field: value
paires).
Ensuite, vous définissez collections sur le serveur Meteor avec
MyCollection = new Mongo.Collection('collection-name-in-mongo')
Ces collections contiennent toutes les données des collections MongoDB, et vous pouvez y exécuter MyCollection.find({...})
, ce qui retournera un curseur (un ensemble d'enregistrements, avec des méthodes pour les parcourir et les renvoyer).
Ce curseur est (la plupart du temps) utilisé pour publier (envoyer) un ensemble d'enregistrements (appelé un "jeu d'enregistrements" ). Vous pouvez éventuellement publier uniquement les champs certains de ces enregistrements. Ce sont les jeux d'enregistrements ( pas collections) que les clients abonnement vers. La publication est effectuée par une fonction de publication , qui est appelée chaque fois qu'un nouveau client s'abonne et qui peut prendre des paramètres pour gérer les enregistrements à renvoyer (par exemple, un identifiant utilisateur, pour renvoyer uniquement les documents de cet utilisateur).
Sur le client, vous avez Minimongo collections qui partiellement miroir certains des enregistrements de le serveur. "En partie" car ils peuvent contenir uniquement certains des champs et "certains des enregistrements" car vous souhaitez généralement envoyer au client uniquement les enregistrements dont il a besoin, pour accélérer le chargement des pages et uniquement ceux dont il a besoin et a la permission d'accéder.
Minimongo est essentiellement une implémentation en mémoire non persistante de Mongo en JavaScript pur. Il sert de cache local qui stocke uniquement le sous-ensemble de la base de données avec lequel ce client travaille. Les requêtes sur le client (find) sont servies directement depuis ce cache, sans parler au serveur.
Ces collections Minimongo sont initialement vides. Ils sont remplis par
Meteor.subscribe('record-set-name')
appels. Notez que le paramètre to subscribe n'est pas un nom de collection; c'est le nom d'un jeu d'enregistrements que le serveur a utilisé dans l'appel publish
. L'appel subscribe()
abonne le client à un jeu d'enregistrements - un sous-ensemble d'enregistrements de la collection de serveurs (par exemple, les 100 derniers articles de blog), avec tout ou un sous-ensemble dans chaque enregistrement (par exemple uniquement title
et date
). Comment Minimongo sait-il dans quelle collection placer les enregistrements entrants? Le nom de la collection sera l'argument collection
utilisé dans les rappels added
, changed
et removed
du gestionnaire de publication, ou si ceux-ci sont manquants (qui est le cas la plupart du temps), ce sera le nom de la collection MongoDB sur le serveur.
C'est là que Meteor rend les choses très pratiques: lorsque vous modifiez un enregistrement (document) dans la collection Minimongo sur le client, Meteor mettra instantanément à jour tous les modèles qui en dépendent et renverra également les modifications au serveur, qui à son tour stockera les modifications dans MongoDB et les enverra aux clients appropriés qui se sont abonnés à un jeu d'enregistrements comprenant ce document. C'est ce qu'on appelle compensation de latence et est l'un des sept principes fondamentaux de Meteor .
Vous pouvez avoir un tas d'abonnements qui tirent des enregistrements différents, mais ils se retrouveront tous dans la même collection sur le client s'ils proviennent de la même collection sur le serveur, en fonction de leur _id
. Ceci n'est pas expliqué clairement, mais sous-entendu par les documents Meteor:
Lorsque vous vous abonnez à un jeu d'enregistrements, il indique au serveur d'envoyer des enregistrements au client. Le client stocke ces enregistrements dans des collections Minimongo locales, avec le même nom que l'argument
collection
utilisé dans les rappelsadded
,changed
etremoved
du gestionnaire de publication . Meteor mettra en file d'attente les attributs entrants jusqu'à ce que vous déclariez Mongo.Collection sur le client avec le nom de collection correspondant.
Ce qui n'est pas expliqué, c'est ce qui se passe lorsque vous ne faites pas utilisez explicitement added
, changed
et removed
, ou publiez des gestionnaires du tout - ce qui est la plupart du temps. Dans ce cas le plus courant, l'argument de collection est (sans surprise) tiré du nom de la collection MongoDB que vous avez déclarée sur le serveur à l'étape 1. Mais cela signifie que vous pouvez avoir différentes publications et abonnements avec des noms différents, et tous les les enregistrements se retrouveront dans la même collection sur le client. Jusqu'au niveau des champs de niveau supérieur , Meteor prend soin d'effectuer une union définie entre les documents, de sorte que les abonnements peuvent se chevaucher - publier des fonctions qui expédient différents top les champs de niveau au client travaillent côte à côte et sur le client, le document dans la collection sera le nion des deux ensembles de champs .
Vous avez une collection BlogPosts, que vous déclarez de la même manière sur le serveur et le client, même si cela fait des choses différentes:
BlogPosts = new Mongo.Collection('posts');
Sur le client, BlogPosts
peut obtenir des enregistrements de:
un abonnement aux 10 articles de blog les plus récents
// server
Meteor.publish('posts-recent', function publishFunction() {
return BlogPosts.find({}, {sort: {date: -1}, limit: 10});
}
// client
Meteor.subscribe('posts-recent');
un abonnement aux messages de l'utilisateur actuel
// server
Meteor.publish('posts-current-user', function publishFunction() {
return BlogPosts.find({author: this.userId}, {sort: {date: -1}, limit: 10});
// this.userId is provided by Meteor - http://docs.meteor.com/#publish_userId
}
Meteor.publish('posts-by-user', function publishFunction(who) {
return BlogPosts.find({authorId: who._id}, {sort: {date: -1}, limit: 10});
}
// client
Meteor.subscribe('posts-current-user');
Meteor.subscribe('posts-by-user', someUser);
un abonnement aux articles les plus populaires
Tous ces documents proviennent de la collection posts
dans MongoDB, via la collection BlogPosts
sur le serveur, et se retrouvent dans la collection BlogPosts
sur le client.
Nous pouvons maintenant comprendre pourquoi vous devez appeler find()
plus d'une fois - la deuxième fois sur le client, car les documents de tous les abonnements se retrouveront dans la même collection, et vous devez récupérer uniquement ceux qui vous intéressent à propos. Par exemple, pour obtenir les publications les plus récentes sur le client, il vous suffit de refléter la requête depuis le serveur:
var recentPosts = BlogPosts.find({}, {sort: {date: -1}, limit: 10});
Cela ramènera un curseur sur tous les documents/enregistrements que le client a reçus jusqu'à présent, à la fois les premiers messages et les messages de l'utilisateur. ( merci Geoffrey ).
Oui, la fonction find () côté client renvoie uniquement les documents se trouvant sur le client dans Minimongo. De docs :
Sur le client, une instance Minimongo est créée. Minimongo est essentiellement une implémentation en mémoire non persistante de Mongo en JavaScript pur. Il sert de cache local qui stocke uniquement le sous-ensemble de la base de données avec lequel ce client travaille. Les requêtes sur le client (find) sont servies directement depuis ce cache, sans parler au serveur.
Comme vous le dites, publish () spécifie les documents que le client aura.
La règle de base est ici publish
et subscribed
les noms de variables doivent être les mêmes côté client et côté serveur.
Les noms de collections sur Mongo DB et côté client doivent être identiques.
Supposons que j'utilise la publication et l'abonnement à ma collection nommée employees
alors le code ressemblerait à
Ici, l'utilisation du mot clé var
est facultative (utilisez ce mot clé pour rendre la collection locale à ce fichier).
CollectionNameOnServerSide = new Mongo.Collection('employees');
Meteor.publish('employeesPubSub', function() {
return CollectionNameOnServerSide.find({});
});
CollectionNameOnClientSide = new Mongo.Collection('employees');
var employeesData = Meteor.subscribe('employeesPubSub');
Template.templateName.helpers({
'subcribedDataNotAvailable' : function(){
return !employeesData.ready();
},
'employeeNumbers' : () =>{
CollectionNameOnClientSide.find({'empId':1});
}
});
Ici, nous pouvons utiliser la méthode d'assistance subcribedDataNotAvailable
pour savoir si les données sont prêtes côté client, si les données sont prêtes, puis imprimer les numéros d'employé à l'aide de la méthode d'assistance employeeNumbers
.
<TEMPLATE name="templateName">
{{#if subcribedDataNotAvailable}}
<h1> data loading ... </h1>
{{else}}
{{#each employeeNumbers }}
{{this}}
{{/each}}
{{/if}}
<TEMPLATE>
// on the server
Meteor.publish('posts', function() {
return Posts.find();
});
// on the client
Meteor.subscribe('posts');