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, но объект напрямую.

Другие вопросы по тегам