collectionCount не отображает значение в шаблоне / meteor-rxjs в сервисе
Это мое первое представление в SO, поэтому, если что-то не так или не в нужном месте, пожалуйста, не стесняйтесь, сообщите мне.
Теперь на мой вопрос:
Я пытаюсь реализовать сервис в простом приложении, основанном на шаблонной базе метеоров Angular2.
Рассмотрим следующий код, где я пытаюсь сделать две вещи:
- Показать список списков дел (<- Это работает)
- Показать количество списков, представленных с .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 и добавляю его в массив provider.
Затем в моем 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()- к методу, определенному в моем сервисе, например
getListsCount() {
return this.numLists$.zone();
}
- Попробовал то же самое (это добавление оператора.zone()-) в метод службы initListSubscription(), где я делаю вещи, когда подписка готова
- Пробовал то же самое в моем компоненте, когда я звоню
// 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, вы классные.
1 ответ
Я заметил, что ObservableCursor
(как возвращено myCollection.find()
) необходимо подписаться, прежде чем иметь какое-либо влияние. Я предполагаю, что мы описываем это как наблюдаемую холодность.
В простых ситуациях (например, передача курсора напрямую через шаблон через AsyncPipe) Angular выполняет подписку самостоятельно (как часть async
процесс трубы).
Так что в вашем случае вам просто нужно получить ссылку на промежуточный объект, возвращаемый find()
, прежде чем подать заявку collectionCount()
на него, так что вы можете подписаться на него:
const cursor = Todolists.find({});
this.numLists$ = cursor.collectionCount();
this.numLists$.subscribe((numberOfLists) => {
console.log(numberOfLists);
});
cursor.subscribe(); // The subscribe that makes it work.
Тогда вы можете использовать numLists$
через AsyncPipe в вашем шаблоне:
{{ numLists$ | async}}
Или вы можете использовать простой промежуточный заполнитель, который вы назначаете в numLists$.subscribe()
private numLists: number;
// ...
this.numLists$.subscribe((numberOfLists) => {
this.numLists = numberOfLists;
});
и в вашем шаблоне: {{numLists}}
Что касается реактивности, вам не нужно MeteorObservable.autorun()
чтобы обернуть функции, которые просто переназначают Observable: AsyncPipe будет правильно использовать Observable и реагировать соответственно.
Ситуация отличается для findOne()
, который не возвращает Observable, но объект напрямую.