Return subject only for subscription - javascript

my question is regarding returning a reference to a subject without allowing the receiver to do .next() on the subject.
For instance, I have a service which contains a subject and can trigger new events on the subject
class ExampleService {
private exampleSubject = new Subject<boolean>;
// Ideally the only way to call next on the subject
doNext() {
this.exampleSubject.next(true);
}
getSubject() {
// Ideally returning a reference to the subject but only with the
// ability to subscribe and listen for events, not to call .next()
return this.exampleSubject;
}
}
Again, what I am looking for is a way to have other components call this service and get the subject but only be able to subscribe and listen for changes, they should not be able to make changes.
ExampleService.getSubject().subscribe(() => {
//do something
}) // ok
ExampleService.getSubject().next(true) // error/not allowed

The officially recommended way of doing this (assuming you're using TypeScript) is to force retype the Subject to an Observable (Subject is Observable like any other):
class ExampleService {
private exampleSubject = new Subject<boolean>();
observable$: Observable<boolean> = this.exampleSubject;
...
}
Now observable$ can be public because it's just a regular Observable. TypeScript won't allow you to call eg ExampleService.observable$.next() because this method doesn't exist on Observables.
If you're using just JavaScript you can use exampleSubject.asObservable() to return Observable from a Subject.
This discussion on RxJS's GitHub is also relevant: https://github.com/ReactiveX/rxjs/pull/2408

Related

What is the difference between subscribing to subject and subscribing to asObservable?

What is the difference between subscribing to subject and subscribing to asObservable?
What is the difference between the following?
this.subject.subscribe((data) => this.datas.push(data));
this.subject.asObservable().subscribe((data) => this.datas.push(data));
Seems like both are same!
From the subscription side, there is no difference; emissions are received exactly the same.
asObservable() is used to hide the Observer behavior from consumers, preventing them from nexting values into the subject.
In angular, you'll see this in a lot services where you want consumers of the service to have access to the emitted values, but you don't want them to be able to call .next on the subject:
class SomeService {
private subject$ = new Subject();
public observable$ = this.subject$.asObservable();
}

How to pass a value from a service, to a component's method

I've got a service that I use to share data between 2 components. That part works flawlessly, but now I need to call a method of component A, when something triggers on the service (and pass a value to that component). How can I do this? I read on older questions that this is a wrong approach but since Im a noob I dont know what to search for a solution.
Do I need to use observables?
I think Joseph's idea is the way to go.
Here's how I'd implement it:
class FooService {
private _newEvents = new Subject();
newEvents$ = this._newEvents.asObservable();
addNewEvent (ev) {
this._newEvents.next(e);
}
}
// Allow `A` class to communicate with `B` class
class A {
addEvent (ev) {
this.fooService.addNewEvent(ev);
}
}
class B {
private subscription: Subscription;
ngOnInit () {
this.subscription = this.fooService.newEvents$
.subscribe(e => {})
}
ngOnDestroy () {
this.subscription.unsubscribe();
}
}
Note that if your B class subscribes to multiple observables, you should unsubscribe from them using, among other solutions, takeUntil.
Observables / Subjects are one way. You would have one Subject in the service, and would use .next(value) on it to exchange values. Each component which is interested in the value may subscribe to that subject.
Example: (taken from RxJS docs
//your Service
import { Subject } from 'rxjs';
const subject = new Subject<number>();
//Component A (and others as well)
service.subject.subscribe({
next: (num) => console.log(num)
});
//this should work as well with prettier syntax:
service.subject.subscribe(sum =>
console.log(num)
);
//Component B
service.subject.next(7) //passing number 7 to Component A
Whenever you create a subscription, make sure to always unsubscribe! Else you might end up with stacks of subscriptions, which will all get triggered simultaneously in the very same component.
From personal experience, I found it more helpful to outsource any functions and variables that could be considered as global into a dedicated service, if possible. If you directly read the variables of a service from your components (and modify them if necessary), you'll have the same effect. That works as long as you keep a proper service structure. Some examples of dedicated services with global use are:
Translations (TranslationService)
Rights Management (PermissionService)

I need to return a String but only have an Observable

Another Observable question.
I'm working with angular translation service and Kendo.UI components.
According to the Kendo.UI documentation, I can implement a MessageService to translate its components.
To do so, I have to extend an abstract class that contains a method that return a string:
#Injectable()
export class MyMessageService extends MessageService {
private messages = {'kendo.upload.select': 'initial value'}
constructor(private readonly translate: TranslateService) {
super();
this.translate.get('my_translation_key')
.subscribe((value) => {
this.messages['kendo.upload.select'] = value;
});
}
public get(key: string): string {
return this.messages[key];
}
}
The problem is, the moment kendo injects my custom service, the observables were not resolved yet and my button never get its value changed from "'initial value'" to the translated.
From my understatement, kendo's service should accept an observable, not a string. But I'm not very familiar with promises/subjects/observables.
Is there a workaround for this?
ps.: I know TranslateService has a .instant(key) method, but the same way, the values are not loaded yet.
It sounds like you are saying it the observable is pushed before kendo subscribes. If that is the case then you should use a ReplaySubject instead of an Observable as the Replaysubject will push all the values that were given to the observable to the subscriber when it subscribes.You can tell it how many observed values to replay.
Also you could try instead to call your server after view init or even OnInint. Calling the service in the constructor is not recommended as the view is not done rendering and any html async pipes may not be set yet. Worst case you can use the Replay subject.

Using an Observable to detect a change in a variable

I think I misunderstand how Observables are supposed to be used. I want to put a value in, and when the value changes it should emit the new value. I thought that was what they were for, but all the tutorials and docs don't seem to do this, but at the same time, I always see them being applied this way. For example, in angular when you subscribe to a "FirebaseListObservable", when the value in firebase changes it fires off a snapshot in the subscription. I want to make that for my own variable. Let's say I just have a string variable, and when it changes, it fires off any subscriptions.
Normally I would have my observables in services that get subscribed to in components, but I bundled them all in one class for the convenience of this answer. I've listed comments explaining each step. I hope this helps. : )
import { Subject } from 'rxjs/Subject';
export class ClassName {
// ------ Creating the observable ----------
// Create a subject - The thing that will be watched by the observable
public stringVar = new Subject<string>();
// Create an observable to watch the subject and send out a stream of updates (You will subscribe to this to get the update stream)
public stringVar$ = this.stringVar.asObservable() //Has a $
// ------ Getting Your updates ----------
// Subscribe to the observable you created.. data will be updated each time there is a change to Subject
public subscription = this.stringVar$.subscribe(data => {
// do stuff with data
// e.g. this.property = data
});
// ------ How to update the subject ---------
// Create a method that allows you to update the subject being watched by observable
public updateStringSubject(newStringVar: string) {
this.stringVar.next(newStringVar);
}
// Update it by calling the method..
// updateStringSubject('some new string value')
// ------- Be responsible and unsubscribe before you destory your component to save memory ------
ngOnDestroy() {
this.subscription.unsubscribe()
}
}
Try this using ReplySubject. I used typescript, angularfire to explain in the below code example
export class MessageService {
private filter$: ReplaySubject<any> = new ReplaySubject(1);
getMessagesOfType():FirebaseListObservable<any>{
return this.af.database.list(this.messagesPath, {
query: {
orderByChild: 'type',
equalTo: this.filter$
}
});
}
getClosedMessage(): void {
this.filter$.next('closed');
}
getOpenMessage(): void {
this.filter$.next('open');
}
}
// in some other class
// MessagesSubject is assigned to messageService
this.messageService.getMessagesOfType().subscribe((listOfMessages)=>{
// this list will be updated when the this.filter$ updated see below functions
console.log(listOfMessages);
});
// update this.filter$ like this to get
this.messageService.getClosedMessage();
// to get open messges in
this.messageService.getOpenMessage();

What are RxJS Subject's and the benefits of using them?

I found the rxJS docs define them as
What is a Subject? An RxJS Subject is a special type of Observable that allows values to be multicasted to many Observers. While plain Observables are unicast (each subscribed Observer owns an independent execution of the Observable), Subjects are multicast.
and it goes on to give examples but I'm looking for a basic ELI5 explanation. From my understanding is it helps handle and define items in a sequence. Is that correct?
I think it would be most helpful to me and others to see a simple function with and without defining an rxJS Subject to understand why it's important?
Thanks!
The easiest way to understand it is to think of a Subject as both a producer and a consumer. It's like an open channel where someone can send a message on one end, and any subscribers will receive it on the other end.
+---------------
Sender | => => => => Subscriber
-----------------------+ +-----------
Message => => => => => => => => => => => Subscriber
-----------------------+ +-----------
| => => => => Subscriber
+---------------
In code terms say you have a service with a subject
class MessageService {
private _messages = new Subject<Message>();
get messages: Observable<Message> {
return this._messages.asObservable();
}
sendMessage(message: Message) {
this._messages.next(message);
}
}
Note the messages getter returning the Subject as an Observable. This is not required. The Subject is already an observable, and anybody could subscribe directly to the Subject. But I think the asObservable pattern is used as a way to limit what users can do with it, i.e. so users only use it to subscribe to, and not emit to. We save the emitting for the sendMessage method.
Now with this service in place, we can inject it into different components, and this can be a way for two (or more) arbitrary components to communicate (or just receive arbitrary event notifications).
class ComponentOne {
constructor(private messages: MessageService) {}
onClick() {
this.messages.sendMessage(new Message(..));
}
}
class ComponentTwo {
constructor(private messages: MessageService) {}
ngOnInit() {
this.messages.messages.subscribe((message: Message) => {
this.message = message;
});
}
}
Angular's own EventEmitter is actually a Subject. When we subscribe to the EventEmitter, we are subscribing to a Subject, and when we emit on the EventEmitter, we are sending a message through the Subject for all subscribers.
See also:
Subject vs BehaviorSubject vs ReplaySubject in Angular
Subjects are useful when the code you're in is the one that is actually originating the observable data. You can easily let your consumers subscribe to the Subject and then call the next() function to push data into the pipeline.
If, however, you are getting data from other source and are just passing it along (perhaps transforming it first), then you most likely want to use one of the creation operators shown here, such as Rx.Observable.fromEvent like so:
var clicks = Rx.Observable.fromEvent(document, 'click');
clicks.subscribe(x => console.log(x));
This allow you to stay in the functional paradigm, whereas using a Subject, while it has its uses, is considered by some to be a smell that you're trying to force imperative code into a declarative framework.
Here is a great answer that explains the difference in the two paradigms.
If you want the most simple explanation ...
Observables are usually the result of something. The result of an http call, and whatever you do with a pipe returns an observable.
But what is the source of those things? Ever wondered how you hook your user events into the whole rxjs thing? The main feature of subjects is that you can call the next() method on them.
When doing reactive programming, the first step is usually to make a list of possible subject you will have.
For instance: lets say we have to make a todo-list app.
We will probably have a couple of variables in our component:
public deleteItem$ = Subject<TodoItem> = new Subject();
public addItem$ = Subject<TodoItem> = new Subject();
public saveList$ = Subject<TodoItem[]> = new Subject();
and in our applicatiuon we will hook these up like this:
<button (click)="deleteItem$.next(item)">Delete</button>
Using rxjs, we will use operators like merge/combineLatest/withLatestFrom to handle these subjects and define our application logic.
I'll see if I can find the time to make a small example.
You can find a study of the semantics of subjects here.
All answered I see are correct. I'll just add that the term subject comes from the observer pattern (cf. https://en.wikipedia.org/wiki/Observer_pattern). As such a subject is sort of a relay, it receives something on one end, and emit it on any of its ends (subscriptions).

Categories

Resources