Urigo / meteor-rxjs

Exposing Mongo Cursor as RxJS Observable
MIT License
120 stars 37 forks source link

How to use this package with services and some side effects #17

Closed jalalat closed 7 years ago

jalalat commented 8 years ago

Hi @barbatus

Can you please suggest how it is possible to use this package within services. I tried to use it but got no results.

export class NotificationDataService extends MeteorComponent implements OnInit {
    user: Meteor.User;
    notifications: Observable < Notification[] > ;
    data: Notification[];
    notificationSub: Subscription;
    constructor(private ngZone: NgZone) {
        super();
    }
    ngOnInit() {
    }
    subscribeNotifications(userId: string): Observable < Notification[] > {
       let notificationsOptions = {
            sort: { updatedAt: -1 }
        };

        this.notificationSub = MeteorObservable
            .subscribe('notifications', notificationsOptions, userId)
            .subscribe(() => {
                    this.notifications = Notifications.find({ notificationToId: userId },
                        notificationsOptions)
                    .debounce(() => Observable.interval(50))
                    .zone();
                return this.notifications;
            });
        return this.notifications;    
    }
    getSubscription(){
        return this.notifications;
    }
}

When i tried to call subscribeNotifications(userId) i get this.notifications undefined in my Component. I even tried to wrap the service call in MeteorObservable.autorun() and ngZone.run(); no results.

this.autorunSub = MeteorObservable.autorun().subscribe(() => {
                this.ngZone.run(() => {
                    this.notifications = this.notificationDataService.subscribeNotifications(userId);
                    if(this.notifications){
                        this.notifications.subscribe(data => {
                            this.data = data.sort(function(a, b) {
                                if (a.updatedAt > b.updatedAt) {
                                    return -1;
                                } else if (a.updatedAt < b.updatedAt) {
                                    return 1;
                                }
                                return 0;
                            });
                        }, (error) => {

                        });
                    } else
                    console.log("notification not ready");                       
                });
            });
ayazhussein commented 7 years ago

+1 , Need help too..

dotansimha commented 7 years ago

@jalalat @ayazhussein Can you explain what exactly do you need?

Start with creating the Meteor subscription to the data, and then expose the cursor result as Observable, and you Component will inject the data as service. You can also expose events if you want to create loading/spinner (example isSubscriptionReady).

If you wan't to use it within services, it simply the same idea:

import { Injectable } from "@angular/core";
import { MeteorObservable } from "meteor-rxjs";
import { Observable } from "rxjs";

@Injectable()
class MyService {
    private _isSubscriptionReady: boolean = false;
    private _subscriptionHandler;

    constructor() {

    }

    initSubscription(): void {
        if (!this._subscriptionHandler) {
            this._subscriptionHandler = MeteorObservable.subscribe("myData").subscribe(() => {
                this._isSubscriptionReady = true;
            });
        }
    }

    getData(selector, options): Observable<any> {
        return MyCollection.find(selector, options);
    }

    disposeSubscription(): void {
        if (this._subscriptionHandler) {
            this._subscriptionHandler.unsubscribe();
            this._subscriptionHandler = undefined;
        }
    }

    get isSubscriptionReady() {
        return this._isSubscriptionReady;
    }
}

Am I missing something? Is there other logic you trying to achieve?

ayazhussein commented 7 years ago

that is what I wanted to achieve, but I can't get the correct result here

get isPartiesSubReady() : boolean {
        return this._isPartiesSubReady;
    }

always returns a false value when I call it in my component

 ngOnInit() {
        this.partyService.subscribeParties();

        console.log(this.partyService.isPartiesSubReady);
        this.partyService.getParties().subscribe(parties => {
            console.log(this.partyService._isPartiesSubReady);
            this.parties = parties;
        });
        console.log(this.partyService.isPartiesSubReady);

    }
dotansimha commented 7 years ago

It depends when do you call it - if you call it inside ngOnInit, it might not be ready yet. Make sure that your subscription ready changes the _isPartiesSubReady first, and then I recommend to use this directly on the view, in order to know that something changes. Also, you can expose Observable for you readiness of the data, and next it when the data is ready, then using it in your Component.

jalalat commented 7 years ago

Thanks @dotansimha your example is working perfectly. Initially i faced the same issue @ayazhussein mentioned and i was not sure where to subscribe for subscription. I created a subject to pass on the data to component whenever subscription is ready.

in my service

private _isSubscriptionReadySubject: Subject <Observable<Notification[]>> = new Subject<Observable<Notification[]>>();
initSubscription(terms): void {
        if (!this._notificationSubHandler) {
             this._notificationSubHandler = MeteorObservable.subscribe(terms.viewName, terms).subscribe(() => {
                this._isSubscriptionReadySubject.next(this.getData(terms));
            });
        }
    }

    getData(terms): Observable<Notification[]> {
        let parameters: Parameters = notificationQueryConstructor(terms);
        return Notifications.find(parameters.selector, parameters.options);
    }

and in my component:

let terms = {
                userId: this.user._id,
                viewName: 'notifications',
                sort: { updatedAt: -1 }
            };
            this.notificationService.initSubscription(terms);
            this.notificationService.isSubscriptionReady().subscribe((data) => {
                this.notifications = data.debounce(() => Observable.interval(50)).zone();
            });
samy-59 commented 7 years ago

Hi @jalalat ,

I tried to use your code, but I can't display my data. I have verified and my Minimongo is synchronised.

My service :

private _isSubscriptionReadySubject: Subject <Observable<Instruction[]>> =
    new Subject<Observable<Instruction[]>>();

public subscribe(options): void {
    if (!this._subscriptionHandler) {
            this._subscriptionHandler = MeteorObservable.subscribe('instructions', options)
                .subscribe(() => {
                    this._isSubscriptionReady = true;
                     this._isSubscriptionReadySubject.next(this.getData({}, options));
                });
    }
}

public getData(selector, options): Observable<Instruction[]> {
    return Instructions.find(selector, options);
}

My component :

public instructions$: Observable<Instruction[]>;

ngOnInit() {
    const options = {
        sort: {
            title: -1
        }
    };
    this.instructionService.subscribe(options);
    this.instructionService.isSubscriptionReady().subscribe((data) => {
        this.instructions$ = data.debounce(() => Observable.interval(50)).zone();
    });
}

My view :

<ul>
    <li *ngFor="let item of instructions$ | async">{{ item.title }}</li>
</ul>

Any idea ?

jalalat commented 7 years ago

Hi @samy-59

My this.notificationService.isSubscriptionReady() returns subject and not a boolean.

isSubscriptionReady(): Subject <Observable<Notification[]>> {
        return this._isSubscriptionReadySubject;
    }    

I do not have any boolean to indicate if subscription is ready. Rather i am using the Subject and subscribing to it in my component.

JanvanCasteren commented 7 years ago

This is an old discussion, but since I struggled with this as well, I will post my solutions here. I put as much of the Meteor-specific code in data services, so I won't have to change much in components when would decide to move away from Meteor.

My collection:

import { MongoObservable } from "meteor-rxjs";
import {Demo} from "../models/demo.model";

export const Demos = new MongoObservable.Collection<Demo>('demos');

My service:


import {Injectable, OnDestroy} from '@angular/core';
import {MeteorObservable, ObservableCursor} from 'meteor-rxjs';
import {Demo} from '../../../../both/models/demo.model';
import {Demos} from '../../../../both/collections/demos';
import {Subscription} from 'rxjs/Subscription';

@Injectable()
export class DemoDataService implements OnDestroy {

    private demoSubscription: Subscription;
    private demoDataSubscription: Subscription;

    public data: ObservableCursor<Demo>;

    constructor() {
        this.demoSubscription = MeteorObservable.subscribe<any>('demos').subscribe(() => {
            this.demoDataSubscription = MeteorObservable.autorun().subscribe(() => {
                this.data = Demos.find();
            });
        });
    }

    ngOnDestroy() {
        this.demoSubscription.unsubscribe();
        this.demoDataSubscription.unsubscribe();
    }
}
My component:

import {Component} from '@angular/core';
import { DemoDataService } from "./demo-data.service";
import template from "./demo.component.html";
import style from "./demo.component.scss";
import {ObservableCursor} from 'meteor-rxjs';
import {Demo} from '../../../../both/models/demo.model';

@Component({
  selector: "demo",
  template,
  styles: [ style ]
})
export class DemoComponent {

  constructor(private demoDataService: DemoDataService) {
  }

  get demoData(): ObservableCursor<Demo> {
    return this.demoDataService.data;
  }

}

and the components template:


<ul>
    <li *ngFor="let item of demoData | async">
        {{item.name}} {{item.age}}
    </li>
</ul>