NOTE: Using ngrx/store (Redux Store) and ngrx/effects (Redux Saga), the way will be different. Check the tutorial Reactive Programming (RxJS 5, Observable) and Redux (Store, Saga) in Angular2-Meteor.
Since RxJS 5 is built in Angular 2, we should take advantage of it.
This tutorial is about how to subscribe data in service using RxJS 5. To understand this tutorial, you need have very basic RxJS knowledge.
First letās explain BehaviorSubjects
a little bit.
BehaviorSubjects
have all the functionality of Subject
, but also
- output the last value received to all observers upon subscription. (The main reason I choose it)
- allow for an initial value to be set
Check here for more info about the difference among Subject
, BehaviorSubject
, ReplaySubject
, AsyncSubject
. When you use, you need pick the right one which fits your needs.
Real-time data: currentUser
Here is the user service:
user.service.ts
import { Injectable } from '@angular/core';
import { Meteor } from 'meteor/meteor';
import { MeteorComponent } from 'angular2-meteor';
import { BehaviorSubject } from 'rxjs/BehaviorSubject';
@Injectable()
export class UserService extends MeteorComponent {
currentUser$ = new BehaviorSubject<User>(null);
currentUserId: string;
constructor() { super(); }
// You can move this part into constructor(), but for me, to call it explicitly is more clear to me.
// Also, next example you will see for some data, you only need subscribe when you need.
subscribeCurrentUser() {
this.subscribe('users.currentUser', () => {
this.autorun(() => {
this.currentUser$.next(<User>Meteor.user()); // <User> here is to convert the type Meteor.User to my custom interface User
this.currentUserId = Meteor.userId();
});
});
}
}
For this example, I use currentUserId
a lot in my app, so I put a currentUserId
there too. But you donāt have to.
We subscribe currentUser data only one time when the website starts, instead of subscribe and unsubscribe in each component which solves a big performance issue.
// app.ts
@Component({ ... })
class App implements OnInit {
constructor (
private _userService: UserService) {
super();
}
ngOnInit() {
this._userService.subscribeCurrentUser();
}
}
When you need use currentUser information in the component:
// randam.component.ts
@Component({ ... })
export class RandomComponent implements OnInit, OnDestroy {
currentUser: User;
ngOnInit() {
this.subscription = this._userService.currentUser$.subscribe(currentUser => {
if (!currentUser) return; // don't forget this, because you may subscribe the data before you got data from the server
this.currentUser = currentUser;
// do other things
});
}
ngOnDestroy() {
this.subscription.unsubscribe();
}
}
If you are familiar with RxJS 5 Operators, you can change if (!currentUser) return;
to .filter(x => !!x)
, like below:
this.subscription = this._userService.currentUser$
.filter(x => !!x)
.subscribe(currentUser => {
this.currentUser = currentUser;
// do other things
});
BehaviorSubjects also allows for an initial value. So sometimes you can give it an initial value, such as:
isOpen = new BehaviorSubject<boolean>(false);
Not real-time data
Sometimes, you donāt want real-time data or you donāt want to keep that data all the time. So subscribe it without autorun
. But then you need unsubscribe it at the time you donāt want it manually to avoid wasting resources.
Also, the sample below shows how to subscribe with parameters (product code here) if you need.
import { Injectable } from '@angular/core';
import { Mongo } from 'meteor/mongo';
import { MeteorComponent } from 'angular2-meteor';
import { BehaviorSubject } from 'rxjs/BehaviorSubject';
import { Products } from '../collections/products';
@Injectable()
export class ProductsService extends MeteorComponent {
products: Mongo.Cursor<Product>;
products$ = new BehaviorSubject<Mongo.Cursor<Product>>(null);
subscription: any;
constructor() { super(); }
subscribeProducts(code: string) {
this.subscription = this.subscribe('products.private', code, () => {
this.products = Products.find({ code: code });
this.products$.next(this.products);
// Or just: this.products$.next(Products.find({ code: code }));
}, true);
}
unsubscribeProducts() {
if (this.subscription) this.subscription.stop();
}
}
Note
Some data is only needed in only one component, actually you can subscribe the data in that component.
However, we try to avoid that, instead, we subscribe in the service. The reason is because we want to use dependency injection as a pattern. It can help the code Testable and Reusable.
- Testable: When we test, we can switch to another service and inject fake data in that component.
- Reusable: In the future, for example, if we want to switch to Apollo to take advantage of GraphQL, we just need rewrite our service. But we can still use same component.
Read this article to have a deep understanding of Dependency Injection.
This is just how to use Service to subscribe data, but Service has many other usages, such as passing data between two components, which are not covered in this tutorial.
Feel free to comment and improve it.