2017-02-16 8 views
3

Это мое первое подчинение SO, поэтому, если что-то не так или нет в нужном месте, пожалуйста, не стесняйтесь говорить мне.collectionCount не отображает значение в шаблоне/meteor-rxjs в сервисе

Теперь мой вопрос:

Я пытаюсь реализовать услугу в простом приложении к делать на основе 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() - оператор с методом, определенным в моей службе, как
 getListsCount() { 
     return this.numLists$.zone(); 
     } 
  • Попробованного то же самое (что, будучи добавлением .zone() - оператор) в initListSubscription() - метод сервиса, где я делаю вещи, когда подписка готова
  • Пробовал то же самое в моем компоненте, когда я называю
// with the addition of .zone() 
    this.numLists$ = this._todolistService.getListsCount().zone(); 

=====

Добавление .zone() был, с моей точки зрения, как кто-то делает это как хобби, очевидным, что нужно сделать. К сожалению, никакого эффекта. Насколько я понимаю, это придает asynchronus задачу, что происходит в зоне и углам главного в основном такая же, как говорят

constructor(private _zone: NgZone){} 

ngOnInit(){ 
    this._zone.run(() => { 
    //...do stuff here that's supposed to be executed in angulars zone 
    }) 
} 

, например.

Может кто-нибудь помочь мне?Я действительно пытался понять, что происходит, но я не могу окутать голову, почему я не могу получить фактическое количество списков из этого наблюдаемого.

Другое дело, я задаюсь вопросом:

Если бы я сделать все это непосредственно в моем компоненте, и я хотел, чтобы мой список автоматически обновляется, если я добавил новый к-DOS, я бы сделал следующее, чтобы сделать вещи реактивными:

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() это не так.

Я был бы очень признателен, если бы кто-то мог указать мне в правильном направлении. Может быть, мне что-то не хватает, но я думаю, что это теоретически должно работать, так как я могу отображать списки (и даже обновлять их, когда я делаю что-то из своего компонента).

Заранее благодарен!

UPDATE:

Благодаря @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:

После подписки на Наблюдаемый вернулся из службы и получил е значение, я придаю значение переменной и использовать его так:

<span class="badge badge-accent pull-right">{{ numTodoLists }}</span> 

что приводит к

the number of lists displayed

Кроме того, значение обновляется автоматически, как только я добавить новый список - все работает как ожидалось.

Спасибо ТОЧНО и особенно @ghybs, вы потрясающий.

+0

Лучший первый вопрос, который я видел! – chazsolo

+0

@chazsolo Спасибо! Если вам интересно, я обновил свой вопрос с помощью решения, которое работает для меня. – minau87

ответ

1

Я заметил, что ObservableCursor (как вернулся myCollection.find()) должен быть подписан, прежде чем он будет иметь какой-либо эффект. Я думаю, мы описываем это как Холодный Наблюдаемый.

В простых ситуациях (например, прямое перемещение курсора к шаблону через AsyncPipe), Угловая делает свою подписку самостоятельно (как часть процесса трубы 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() обертывания функций, которые просто переназначить наблюдаемый: AsyncPipe будет правильно использовать Observable и реагировать соответствующим образом.

Ситуация другая для findOne(), которая не возвращает объект Observable, кроме объекта.

+0

Большое спасибо, сейчас все работает так, как ожидалось. Я обновил этот вопрос своим фактическим решением. Кроме того, я снова упоминал «Сервис» в названии, так как заметил, что это тема, которая не рассматривается в официальном учебнике. Возможно, это полезно для кого-то в будущем. – minau87