collectionCount 未在服务的模板/meteor-rxjs 中显示值
collectionCount not displaying value in template / meteor-rxjs in a service
这是我第一次提交 SO,所以,如果有任何错误或不正确的地方,请随时告诉我。
现在回答我的问题:
我正在尝试在基于 the Angular2 meteor-base boilerplate 的简单待办事项应用程序中实施服务。
考虑以下代码,其中我尝试做两件事:
- 显示一堆待办事项列表(<- 这可行)
- 显示 .collectionCount() 的列表数量(<- 这不起作用)
todolist.service.ts:
import { Injectable } from '@angular/core';
import { Subscription, Observable } from 'rxjs';
import { MeteorObservable, ObservableCursor } from 'meteor-rxjs';
import { Todolist } from '../../../../../both/models/todolist.model';
import { Todolists } from '../../../../../both/collections/todolists.collection';
@Injectable()
export class TodolistService {
todolistSubscription: Subscription;
todoLists$: Observable<Todolist[]>;
numLists$: Observable<number>;
constructor() {
this.initListSubscription();
}
initListSubscription() {
if (!this.todolistSubscription) {
this.todolistSubscription = MeteorObservable.subscribe("todolists").subscribe(() => {
// Code to be executed when the subscription is ready goes here
// This one works
this.todoLists$ = Todolists.find({}).zone();
this.todoLists$.subscribe((lists) => {
console.log(lists);
});
// This one doesn't
this.numLists$ = Todolists.find({}).collectionCount();
this.numLists$.subscribe((numberOfLists) => {
console.log(numberOfLists);
})
});
}
}
getLists(selector?, options?) {
// Just in case anyone is wondering what those params are for...
// return Todolists.find(selector || {}, options || {});
return this.todoLists$;
}
getListsCount() {
return this.numLists$;
}
unsubscribeFromLists() {
this.todolistSubscription.unsubscribe();
}
}
这个,我导入我的 app.module.ts 并将其添加到提供程序数组中。
然后,在我的 list.component.ts 中,我这样使用该服务:
import { Component, OnInit } from '@angular/core';
import { TodolistService } from '../../shared/todolist.service'
// + all other relevant imports, e.g. Todolist (the model), Todolists (collection)
@Component({
selector: 'list-component',
template,
styles: [style]
})
export class ListComponent implements OnInit{
lists$: Observable<Todolist[]>;
numLists$: Observable<number>;
constructor(private _todolistService: TodolistService){}
ngOnInit(){
// Again, this one works...
this._todolistService.getLists().subscribe((lists) => {
console.log(lists);
});
// ...and this does not
this._todolistService.getListsCount().subscribe((number) => {
console.log(number);
});
// This I can also use in my template, see below...
this.lists$ = this._todolistService.getLists();
// This one I can't
this.numLists$ = this._todolistService.getListsCount();
}
}
todolist.component.html:
例如,在我的模板中,我执行以下操作:
<!-- This one works... -->
<div *ngFor="let list of lists$ | async">{{list._id}}</div>
<!-- This one doesn't... -->
<span class="badge">{{ numLists$ | async }}</span>
我试过的东西:
- 将 .zone()-operator 添加到我的服务中定义的方法中,例如
getListsCount() {
return this.numLists$.zone();
}
- 在服务的 initListSubscription() 方法中尝试了相同的方法(即添加 .zone() 运算符),我在订阅准备就绪时执行操作
- 在我的组件中尝试了同样的方法,当我调用
// with the addition of .zone()
this.numLists$ = this._todolistService.getListsCount().zone();
=====
添加 .zone() 从我的角度来看,作为一个业余爱好的人,这是显而易见的事情。可悲的是,没有任何效果。据我了解,这附加了发生在角度区域的异步任务,基本上与说
相同
constructor(private _zone: NgZone){}
ngOnInit(){
this._zone.run(() => {
//...do stuff here that's supposed to be executed in angulars zone
})
}
例如。
有人可以帮帮我吗?我真的很想了解发生了什么,但我无法理解为什么我无法从该可观察对象中获取列表的实际数量。
我想知道的另一件事:
如果我直接在我的组件中执行所有这些操作,并且我希望我的列表在添加新的待办事项时自动更新,我会执行以下操作以使事情具有反应性:
MeteorObservable.subscribe("todolists").subscribe(() => {
// The additional part that's to be executed, each time my data has changed
MeteorObservable.autorun().subscribe(() => {
this.lists$ = Todolists.find({}).zone();
// with/without .zone() has the same result - that being no result ...
this.listCount$ = Todolists.find({}).collectionCount();
});
});
在这里,我也不知道如何在我的服务中实现反应性。我尝试了这个,并再次针对它正在工作的待办事项列表,但对于 .collectionCount() 它不是。
如果有人能在这里指出正确的方向,我将不胜感激。也许我遗漏了一些东西,但我觉得这在理论上应该可行,因为我能够让列表显示(甚至在我从我的组件中执行操作时进行被动更新)。
提前致谢!
更新:
感谢 @ghybs 我终于找到了可行的解决方案。您可以在下面找到最终代码。
todolist.service.ts:
import { Injectable } from '@angular/core';
import { Observable, Subscription, Subject } from 'rxjs';
import { MeteorObservable, ObservableCursor } from 'meteor-rxjs';
import { Todolist, Task } from '../../../../../both/models/todolist.model';
import { Todolists } from '../../../../../both/collections/todolists.collection';
@Injectable()
export class TodolistService {
todolistSubscription: Subscription;
todoLists$: ObservableCursor<Todolist> = Todolists.find({});
numLists$: Observable<number>;
numLists: number = 0;
subReady: Subject<boolean> = new Subject<boolean>();
init(): void {
if(!this.todolistSubscription){
this.subReady.startWith(false);
this.todolistSubscription = MeteorObservable.subscribe("todolists").subscribe(() => {
this.todoLists$ = Todolists.find({});
this.numLists$ = this.todoLists$.collectionCount();
this.numLists$.subscribe((numberOfLists) => {
console.log(numberOfLists)
});
this.todoLists$.subscribe(() => {
this.subReady.next(true);
});
});
}
}
isSubscriptionReady(): Subject<boolean> {
return this.subReady;
}
getLists(selector?, options?): ObservableCursor<Todolist> {
return this.todoLists$;
}
getListsCount(): Observable<number> {
return this.numLists$;
}
addList(name: string, description: string): Observable<string> {
return MeteorObservable.call<string>("addTodoList", name, description);
}
addTask(listId: string, identifier: string, description: string, priority: number, start: Date, end: Date): Observable<number> {
return MeteorObservable.call<number>("addTask", listId, identifier, description, priority, start, end);
}
markTask(listId: string, task: Task, index: number) : Observable<number> {
return MeteorObservable.call<number>("markTask", listId, task, index);
}
disposeSubscription() : void {
if (this.todolistSubscription) {
this.subReady.next(false);
this.todolistSubscription.unsubscribe();
this.todolistSubscription = null;
}
}
}
dashboard.component.ts:
import { Component, OnInit, OnDestroy } from '@angular/core';
import { Router } from '@angular/router';
import { routerTransition } from '../shared/animations'
import { Observable, Subject } from 'rxjs';
import { ObservableCursor } from 'meteor-rxjs';
import { Todolist } from '../../../../both/models/todolist.model';
import { TodolistService } from '../shared/services/todolist.service';
import template from './dashboard.component.html';
import style from './dashboard.component.scss';
@Component({
selector: 'dashboard',
template,
styles: [style],
animations: [routerTransition()]
})
export class DashboardComponent implements OnInit, OnDestroy {
todoLists$: ObservableCursor<Todolist>;
numTodoLists$: Observable<number>;
numTodoLists: number = 0;
constructor(private _router: Router, private todolistService: TodolistService) {}
ngOnInit() {
this.todolistService.init();
this.todolistService.isSubscriptionReady().subscribe((isReady) => {
if(isReady){
this.todolistService.getListsCount().subscribe((numTodoLists) => {
this.numTodoLists = numTodoLists;
});
}
});
}
sideNavShown: boolean = true;
toggleSideNav() {
this.sideNavShown = !this.sideNavShown;
}
ngOnDestroy() {
this.todolistService.disposeSubscription();
}
}
dashboard.component.html:
订阅从服务返回的 Observable 并接收值后,我将值赋给一个变量并像这样使用它:
<span class="badge badge-accent pull-right">{{ numTodoLists }}</span>
这导致
此外,只要我添加一个新列表,该值就会自动更新 - 一切都按预期工作。
非常感谢,尤其是 @ghybs,你真棒。
我注意到 ObservableCursor
(由 myCollection.find()
编辑 return)需要先订阅才能生效。我想我们将其描述为 Cold Observable。
在简单的情况下(比如通过 AsyncPipe 将光标直接传递给模板),Angular 自己进行订阅(作为 async
管道过程的一部分) .
所以在你的情况下,你只需要在应用 collectionCount()
之前获得对由 find()
编辑的中间对象 return 的引用,这样你就可以订阅它:
const cursor = Todolists.find({});
this.numLists$ = cursor.collectionCount();
this.numLists$.subscribe((numberOfLists) => {
console.log(numberOfLists);
});
cursor.subscribe(); // The subscribe that makes it work.
然后您可以在模板中通过 AsyncPipe 使用 numLists$
:
{{ numLists$ | async}}
或者您可以使用在 numLists$.subscribe()
中分配的简单中间占位符
private numLists: number;
// ...
this.numLists$.subscribe((numberOfLists) => {
this.numLists = numberOfLists;
});
并在您的模板中:{{numLists}}
至于反应性,您不需要 MeteorObservable.autorun()
包装仅 re-assign Observable 的函数:AsyncPipe 将正确使用 Observable 并做出相应的反应。
findOne()
的情况不同,它不是return一个Observable而是直接一个对象。
这是我第一次提交 SO,所以,如果有任何错误或不正确的地方,请随时告诉我。
现在回答我的问题:
我正在尝试在基于 the Angular2 meteor-base boilerplate 的简单待办事项应用程序中实施服务。
考虑以下代码,其中我尝试做两件事:
- 显示一堆待办事项列表(<- 这可行)
- 显示 .collectionCount() 的列表数量(<- 这不起作用)
todolist.service.ts:
import { Injectable } from '@angular/core';
import { Subscription, Observable } from 'rxjs';
import { MeteorObservable, ObservableCursor } from 'meteor-rxjs';
import { Todolist } from '../../../../../both/models/todolist.model';
import { Todolists } from '../../../../../both/collections/todolists.collection';
@Injectable()
export class TodolistService {
todolistSubscription: Subscription;
todoLists$: Observable<Todolist[]>;
numLists$: Observable<number>;
constructor() {
this.initListSubscription();
}
initListSubscription() {
if (!this.todolistSubscription) {
this.todolistSubscription = MeteorObservable.subscribe("todolists").subscribe(() => {
// Code to be executed when the subscription is ready goes here
// This one works
this.todoLists$ = Todolists.find({}).zone();
this.todoLists$.subscribe((lists) => {
console.log(lists);
});
// This one doesn't
this.numLists$ = Todolists.find({}).collectionCount();
this.numLists$.subscribe((numberOfLists) => {
console.log(numberOfLists);
})
});
}
}
getLists(selector?, options?) {
// Just in case anyone is wondering what those params are for...
// return Todolists.find(selector || {}, options || {});
return this.todoLists$;
}
getListsCount() {
return this.numLists$;
}
unsubscribeFromLists() {
this.todolistSubscription.unsubscribe();
}
}
这个,我导入我的 app.module.ts 并将其添加到提供程序数组中。
然后,在我的 list.component.ts 中,我这样使用该服务:
import { Component, OnInit } from '@angular/core';
import { TodolistService } from '../../shared/todolist.service'
// + all other relevant imports, e.g. Todolist (the model), Todolists (collection)
@Component({
selector: 'list-component',
template,
styles: [style]
})
export class ListComponent implements OnInit{
lists$: Observable<Todolist[]>;
numLists$: Observable<number>;
constructor(private _todolistService: TodolistService){}
ngOnInit(){
// Again, this one works...
this._todolistService.getLists().subscribe((lists) => {
console.log(lists);
});
// ...and this does not
this._todolistService.getListsCount().subscribe((number) => {
console.log(number);
});
// This I can also use in my template, see below...
this.lists$ = this._todolistService.getLists();
// This one I can't
this.numLists$ = this._todolistService.getListsCount();
}
}
todolist.component.html:
例如,在我的模板中,我执行以下操作:
<!-- This one works... -->
<div *ngFor="let list of lists$ | async">{{list._id}}</div>
<!-- This one doesn't... -->
<span class="badge">{{ numLists$ | async }}</span>
我试过的东西:
- 将 .zone()-operator 添加到我的服务中定义的方法中,例如
getListsCount() {
return this.numLists$.zone();
}
- 在服务的 initListSubscription() 方法中尝试了相同的方法(即添加 .zone() 运算符),我在订阅准备就绪时执行操作
- 在我的组件中尝试了同样的方法,当我调用
// with the addition of .zone()
this.numLists$ = this._todolistService.getListsCount().zone();
=====
添加 .zone() 从我的角度来看,作为一个业余爱好的人,这是显而易见的事情。可悲的是,没有任何效果。据我了解,这附加了发生在角度区域的异步任务,基本上与说
相同constructor(private _zone: NgZone){}
ngOnInit(){
this._zone.run(() => {
//...do stuff here that's supposed to be executed in angulars zone
})
}
例如。
有人可以帮帮我吗?我真的很想了解发生了什么,但我无法理解为什么我无法从该可观察对象中获取列表的实际数量。
我想知道的另一件事:
如果我直接在我的组件中执行所有这些操作,并且我希望我的列表在添加新的待办事项时自动更新,我会执行以下操作以使事情具有反应性:
MeteorObservable.subscribe("todolists").subscribe(() => {
// The additional part that's to be executed, each time my data has changed
MeteorObservable.autorun().subscribe(() => {
this.lists$ = Todolists.find({}).zone();
// with/without .zone() has the same result - that being no result ...
this.listCount$ = Todolists.find({}).collectionCount();
});
});
在这里,我也不知道如何在我的服务中实现反应性。我尝试了这个,并再次针对它正在工作的待办事项列表,但对于 .collectionCount() 它不是。
如果有人能在这里指出正确的方向,我将不胜感激。也许我遗漏了一些东西,但我觉得这在理论上应该可行,因为我能够让列表显示(甚至在我从我的组件中执行操作时进行被动更新)。
提前致谢!
更新:
感谢 @ghybs 我终于找到了可行的解决方案。您可以在下面找到最终代码。
todolist.service.ts:
import { Injectable } from '@angular/core';
import { Observable, Subscription, Subject } from 'rxjs';
import { MeteorObservable, ObservableCursor } from 'meteor-rxjs';
import { Todolist, Task } from '../../../../../both/models/todolist.model';
import { Todolists } from '../../../../../both/collections/todolists.collection';
@Injectable()
export class TodolistService {
todolistSubscription: Subscription;
todoLists$: ObservableCursor<Todolist> = Todolists.find({});
numLists$: Observable<number>;
numLists: number = 0;
subReady: Subject<boolean> = new Subject<boolean>();
init(): void {
if(!this.todolistSubscription){
this.subReady.startWith(false);
this.todolistSubscription = MeteorObservable.subscribe("todolists").subscribe(() => {
this.todoLists$ = Todolists.find({});
this.numLists$ = this.todoLists$.collectionCount();
this.numLists$.subscribe((numberOfLists) => {
console.log(numberOfLists)
});
this.todoLists$.subscribe(() => {
this.subReady.next(true);
});
});
}
}
isSubscriptionReady(): Subject<boolean> {
return this.subReady;
}
getLists(selector?, options?): ObservableCursor<Todolist> {
return this.todoLists$;
}
getListsCount(): Observable<number> {
return this.numLists$;
}
addList(name: string, description: string): Observable<string> {
return MeteorObservable.call<string>("addTodoList", name, description);
}
addTask(listId: string, identifier: string, description: string, priority: number, start: Date, end: Date): Observable<number> {
return MeteorObservable.call<number>("addTask", listId, identifier, description, priority, start, end);
}
markTask(listId: string, task: Task, index: number) : Observable<number> {
return MeteorObservable.call<number>("markTask", listId, task, index);
}
disposeSubscription() : void {
if (this.todolistSubscription) {
this.subReady.next(false);
this.todolistSubscription.unsubscribe();
this.todolistSubscription = null;
}
}
}
dashboard.component.ts:
import { Component, OnInit, OnDestroy } from '@angular/core';
import { Router } from '@angular/router';
import { routerTransition } from '../shared/animations'
import { Observable, Subject } from 'rxjs';
import { ObservableCursor } from 'meteor-rxjs';
import { Todolist } from '../../../../both/models/todolist.model';
import { TodolistService } from '../shared/services/todolist.service';
import template from './dashboard.component.html';
import style from './dashboard.component.scss';
@Component({
selector: 'dashboard',
template,
styles: [style],
animations: [routerTransition()]
})
export class DashboardComponent implements OnInit, OnDestroy {
todoLists$: ObservableCursor<Todolist>;
numTodoLists$: Observable<number>;
numTodoLists: number = 0;
constructor(private _router: Router, private todolistService: TodolistService) {}
ngOnInit() {
this.todolistService.init();
this.todolistService.isSubscriptionReady().subscribe((isReady) => {
if(isReady){
this.todolistService.getListsCount().subscribe((numTodoLists) => {
this.numTodoLists = numTodoLists;
});
}
});
}
sideNavShown: boolean = true;
toggleSideNav() {
this.sideNavShown = !this.sideNavShown;
}
ngOnDestroy() {
this.todolistService.disposeSubscription();
}
}
dashboard.component.html:
订阅从服务返回的 Observable 并接收值后,我将值赋给一个变量并像这样使用它:
<span class="badge badge-accent pull-right">{{ numTodoLists }}</span>
这导致
此外,只要我添加一个新列表,该值就会自动更新 - 一切都按预期工作。
非常感谢,尤其是 @ghybs,你真棒。
我注意到 ObservableCursor
(由 myCollection.find()
编辑 return)需要先订阅才能生效。我想我们将其描述为 Cold Observable。
在简单的情况下(比如通过 AsyncPipe 将光标直接传递给模板),Angular 自己进行订阅(作为 async
管道过程的一部分) .
所以在你的情况下,你只需要在应用 collectionCount()
之前获得对由 find()
编辑的中间对象 return 的引用,这样你就可以订阅它:
const cursor = Todolists.find({});
this.numLists$ = cursor.collectionCount();
this.numLists$.subscribe((numberOfLists) => {
console.log(numberOfLists);
});
cursor.subscribe(); // The subscribe that makes it work.
然后您可以在模板中通过 AsyncPipe 使用 numLists$
:
{{ numLists$ | async}}
或者您可以使用在 numLists$.subscribe()
private numLists: number;
// ...
this.numLists$.subscribe((numberOfLists) => {
this.numLists = numberOfLists;
});
并在您的模板中:{{numLists}}
至于反应性,您不需要 MeteorObservable.autorun()
包装仅 re-assign Observable 的函数:AsyncPipe 将正确使用 Observable 并做出相应的反应。
findOne()
的情况不同,它不是return一个Observable而是直接一个对象。