I'm attempting to write a custom component that can bind to an observable being passed in through an input and show/hide elements based on the state of the observable. What I'd like to be able to do is something like:
#Input() observable: Observable<any>;
ngOnInit() {
this.observable.onSubscribe(() => {
// show element, run logic on start;
});
this.observable.onCompleteOrNext(() => {
// hide element, run logic on end;
});
}
After pouring over the rxjs documentation, I've found that with let I could do something like:
this.observable.let((o: Observable) => {
// run logic.
return o;
});
But this seems like a bit of a hack, and I also can't figure out how to then run something when the observable completes. I expect the observables to be async, such as an HTTP request, but this component needs to handle it either way.
For the observable completing, I assumed I would be able to do something like the below with the do function:
this.observable.do(() => {
// run logic when observable completes.
// not getting called.
});
But, unless the do function is defined on the observable creation, this is not getting called.
I'm aware Angular2 allows binding the view directly to observables, but I also need the ability to run logic based on the observable, not just show/hide view elements.
My googlefoo is failing me and the rxjs documentation isn't being very enlightening, but I feel like this should be a fairly easy thing to do. Perhaps I am approaching it wrong.
You could provide hook methods within the child component:
export class ChildComponent {
onSubscribe(){}
onNext(){}
onComplete(){}
}
In the parent component, you can use ViewChild to get a reference to the ChildComponent, then subscribe to the observable and call the hook methods at key points:
once you've subscribed
when the observable emits
when the observable completes
.
export class ParentComponent {
#ViewChild('child') child:ChildComponent;
...
this.observable.subscribe(
next => this.child.onNext(),
err => {},
() => this.child.onComplete()
);
this.child.onSubscribe()
}
Live demo
Related
I am trying to understand the observables RxJs and I am using angular framework! I can't understand what is actually happening in 'subscriber function' ,it has a parameter named 'observer', and this parameter has a method in the function body, and its name is next()!can a function parameter own a method? based on which rule?!
and the next question is : what is happening in the 'Observable' class? I think the subscriber function returns or to be more precise, creates and passes a value to the Observable instance! and when we call the subscribe method on 'customIntervalObservable' , it passes that data or value to the subscribe method?
am I right?
const customIntervalObservable = new Observable(function subscriber(
observer
) {
let count = 0;
setInterval(() => {
count++;
observer.next(+count);
if (count > 3) {
observer.error(new Error("count is greater than 3"));
}
}, 1000);
});
this.firstObjSubs = customIntervalObservable.subscribe(
(data: number) => {
console.log(data);
},
(error) => {
console.log(error), alert(error.message);
}
);
}
ngOnDestroy() {
//this.firstObjSubs.unsubscribe();
this.firstObjSubs.unsubscribe();
}
}
Here's a bit more:
You can think of an Observer as something that watches the Observable and reacts to notifications.
What notifications?
Next: When another item is emitted into the Observable
Error: When an error occurs
Complete: When there are no more items to emit
Observer is an object with three functions: one for each type of notification. You can define an Observer in code as shown below. But this is uncommon.
And then pass that Observer object into the subscribe as shown below:
More often, you'll pass either the next callback as shown in the first example below. Or an object with one, two, or three of the callback functions defined as shown in the second example.
The subscribe method tells the Observable stream to start emitting its values. It does not itself emit any values. Think of it as a streaming service, like Disney+ or hulu. You have to first subscribe to the service before you can stream movies.
Does this help?
can a function parameter own a method?
Javascript functions parameters are not typed, so you can pass any value you want - including an object.
what is happening in the 'Observable' class?
I'll give this a try:
An observable is basically just a wrapper around a - subscribe() - function that essentially describes the logic of the observable behaviour.
An observable has the ability to notify subjects - or subscribers, or observers - of changes occuring in its state during its lifecycle. To do so, a contract exists that states that the observable should call a subject next() method. This method should describe the logic for how the subject wants to react to such changes.
When a subject is interested in being notified by an observable of its state changes, it executes the observable subscribe() method, passing itself to it as an argument. This effectively provides the observable the ability to call the subject next() method whenever its logic dictates to do so.
To basically illustrate this in code:
// Function describing the observable logic.
function subscribe(observer){
// Observable logic, including calling observer.next() as many times as the logic dictates to notify the observer of state changes.
}
// Observable wrapper.
let observable = new Observable(subscribe)
// Subject interested in being notified of the observable state changes.
let observer = {
next(value){
// Logic for how to react to notifications from the observable.
}
}
// Effectively execute the observable logic.
observable.subscribe(observer)
For simplicty's sake, I omitted a number of more minor concepts - such as the subject complete() or error() methods, as well as the unsubscribe() function returned by the observable subscribe() function.
I have a more complex version of the following pseudo-code. It's a React component that, in the render method, tries to get a piece of data it needs to render from a client-side read-through cache layer. If the data is present, it uses it. Otherwise, the caching layer fetches it over an API call and updates the Redux state by firing several actions (which theoretically eventually cause the component to rerender with the new data).
The problem is that for some reason it seems like after dispatching action 1, control flow moves to the top of the render function again (starting a new execution) and only way later continues to dispatch action 2. Then I again go to the top of the render, and after a while I get action 3 dispatched.
I want all the actions to fire before redux handles the rerender of the component. I would have thought dispatching an action updated the store but only forced components to update after the equivalent of a setTimeout (so at the end of the event loop), no? Is it instead the case that when you dispatch an action the component is updated synchronously immediately, before the rest of the function where the dispatch happens is executed?
class MyComponent {
render() {
const someDataINeed = CachingProvider.get(someId);
return (
<div>{someDataINeed == null ? "Loading" : someDataINeed }</div>
);
}
}
class CachingProvider {
get(id) {
if(reduxStoreFieldHasId(id)) {
return storeField[id];
}
store.dispatch(setLoadingStateForId(id));
Api.fetch().then(() => {
store.dispatch(action1);
store.dispatch(action2);
store.dispatch(action3);
});
return null;
}
}
In addition to #TrinTragula's very important answer:
This is React behaviour. Things that trigger rerenders that are invoked synchronously from an effect/lifecycle or event handler are batched, but stuff that is invoked asnychronously (see the .then in your code) will trigger a full rerender without any batching on each of those actions.
The same behaviour would apply if you would call this.setState three times in a row.
You can optimize that part by adding batch which is exported from react-redux:
Api.fetch().then(() => {
batch(() => {
store.dispatch(action1);
store.dispatch(action2);
store.dispatch(action3);
})
});
You should never invoke heavy operations inside of a render function, since it's going to be triggered way more than you would like to, slowing down your app.
You could for example try to use the useEffect hook, so that your function will be executed only when your id changes.
Example code:
function MyComponent {
useEffect(() => {
// call your method and get the result in your state
}, [someId]);
return (
<div>{someDataINeed == null ? "Loading" : someDataINeed }</div>
);
}
I'm currently trying to get a Register/Subscribe system to work with RxJs.
The situation is that I have component A with several sub components A1, A2, A3, ... The amount has to be dynamic. What I want to do now is that whenever an event I will call "somethingChanged" occurs (which is already distributed through an Observable) all sub components A1, ... will do some processing and then return some information (a state) as an event I'll call newStates to the parent action A probably using another observable. For this to work the sub components first have to register themselves to the "event manager" as children of A so that these events can be processed accordingly.
First idea
My first idea for this was to use a bufferCount on the newStates observable with the count being the amount of registered sub components. The problem is that the sub component registering and the parent component subscribing to the newStates observable is happening at almost the same time, the parent even being slightly faster which means the amountSub is usually 0 which breaks this attempt.
registerSubComponent() {
amountSub++;
}
getParentObservable() {
return newStates.bufferCount(amountSub).mergeMap();
}
Second idea
The second attempt was to use the somethingChanged Event and use that to initialize a takeLast to get the last items when they should be thrown. The problem is again as i will run into race condition as sub components take longer to throw their newStates events meaning I'll get old values.
registerSubComponent() {
amountSub++;
}
getParentObservable() {
return somethingChanged.map(() => newStates.takeLast(amountSub);
}
Third idea
So currently my only idea would be to catch the newStates event in the event manager, store the states in an array and check everytime if all registered components send them by looking at the array length. When all states are in i could then send the saved states and reset the array.
registerSubComponent() {
amountSub++;
}
getParentObservable() {
return newParentObservable;
}
newStates.subscribe(state => {
savedStates.push(state);
if(savedStates.length == amountSub) {
newParentObservable.next(savedStates);
savedStates = [];
}
});
Is this the only way or am I missing something so it could be done easier/with observables?
Btw: This is all pseudo code as my actual code also has to support multiple parent components in one manager making it cumbersome to read through.
It sounds like you want change detection up the tree. Using the following method with an angular service sounds like it might be what you need:
I found a solution on this guy Jason Watmore's blog that describes using rxjs Observables and Subjects. Using this method allows data changes to easily propagate up and down the angular inheritance tree to any component you want
Jason's Post
Accompanying Plunkr
Briefly:
You declare a service as a provider at the module.ts level with 3 methods:
sendMessage
clearMessage
getMessage
import { Injectable } from '#angular/core';
import { Observable } from 'rxjs';
import { Subject } from 'rxjs/Subject';
#Injectable()
export class MessageService {
private subject = new Subject();
sendMessage(message: string) {
this.subject.next({ text: message });
}
clearMessage() {
this.subject.next();
}
getMessage(): Observable<any> {
return this.subject.asObservable();
}
}
This service needs imports of Observable and Subject from rxjs
In each component you want to share data with:
create a subscription object in the constructor which calls the service.getMessage() function
call rxjs subscription.unsubscribe() in ngOnDestroy for each component so you aren't leaking memory
you can hook in a function to handle the incoming subscription updates
When you have data you want to share with your other components:
Create a public method which calls the service.sendMessage() method
This will send your updated data to each component and fire those functions you've hooked in to handle the changed data
I believe the blog post I linked to and the plunkr from the post say it best and have really helped me move data around efficiently in my own app but if you have any questions I'll do my best to answer them
From the docs, I wrote my container like so
export default InventoryItemsList = createContainer(() => {
const itemsCollection = Meteor.subscribe('allInventoryItems');
const loading = !itemsCollection.ready();
return {
loading,
items: !loading ? InventoryItems.find().fetch() : []
};
}, class InventoryItemsListComponent extends Component {
render() {
let items = this.props.items;
/* some render logic */
return /*... react component template ...*/ ;
}
});
The problem I see is that
The container function is executed many times, thus calling Meteor.subscribe more than once; is that good? Will Meteor just ignore subsequent subscriptions?
According to this tutorial, subscriptions need to be stopped, but the docs do not mention it at all. This does not take care on it's own, does it?
What is the recommended way to stop (i.e. unsubscribe) or resolve the 2 issues that I see from that point?
Is TrackerRact actually better? (yes, I know this is opinionated, but surely there is some form of a convention with meteor react, here!)
1) The container component is a reactive component, so whenever documents are changed or added to the given collection, it's going to call via your container and update the DOM.
2) As far as I know, the container will only subscribe to the collection via the actual component you bind it to. Once you leave that component, the subscriptions should stop.
If you want to unsubscribe directly, you can just call this.props.items.stop() in your componentWillUnmount() method.
Finally, I will have to say that using React specific implementations are always better than using Meteor specific functions (i.e. it's always better to use state variables over Sessions with React, as it's always better to use containers than Tracker.autorun() with React, etc, etc).
About your problem 2), this is how I solve it
1- When you subscribe to something, store those subscriptions references and pass them to the component.
Here an example with 2 subscriptions, but subscribing to only 1, is even easier.
createContainer((props) =>{
const subscription = Meteor.subscribe('Publication1', props.param1);
const subscription2 = Meteor.subscribe('Publication2', props.param1, props.param2);
const loading = !(subscription.ready() && subscription2.ready());
const results1 = loading ? undefined : Collection1.find().fetch();
const results2 = loading ? undefined : Collection2.findOne({a:1});
return {subscriptions: [subscription, subscription2], loading, results1, results2};
}, MyComp);
Then in my component:
class MyComp extends Component {
......
componentWillUnmount() {
this.props.subscriptions.forEach((s) =>{
s.stop();
});
}
....
}
This way, the component will get in props.subscriptions all the subscriptions it needs to stop before unmounting.
Also you can use this.props.loading to know if the subscriptions are ready (of course, you can have 2 different ready1 and ready2 if it helps).
Last thing, after subscribing, if you .find(), dont forget to .fetch(), else the results will not be reactive.
I'm using react and flux (facebook implementation). In my component I should do something like:
componentDidMount () {
UserActions.loadUser(this.props.username);
PublicationActions.findByUser(this.state.userId);
UserStore.on('CHANGE', this._setUser);
// ...
},
The problem is this.state.userId will be defined only when request triggered by UserActions.loadUser is finished.
So the only way of resolving this problem is putting something like:
if (this.state.userId && !this.state.publications) {
PublicationActions.findByUser(this.state.userId);
}
directly to render() method. I don't like this solution cause render() is for rendering and I don't want to mess any business logic in it.
The only acceptable solution I have found is have special component hierarchy for this case like:
<User username={someVar}>
<PublicationsList userId={this.state.userId}/>
</User>
This will enforce PublicationsList to be smart component (with state and so) but I'd like it to be dumb renderer. Is this the only way or there's something better?
You need store which is listening on UserAction.loadUser finish.
You invoke this action.
In your component you register callback which listening your new store.
When your store invoke your callback, then you have in this callback function access to userId from this store. (Im not sure but maybe your UserStore do wiat I write).
In this callback function you invoke
PublicationActions.findByUser(YourStore.getState().userId)
In render function you check if you have userId. If no then show some loading info. If tez then show normal rending data.
You could expand your loadUser method to accept callback with payload as arguments.
ie:
loadUser(username, callback) {
// some ajax stuff
callback(payload);
}
Then, in your jsx file you could write something like this:
componentDidMount () {
UserActions.loadUser(this.props.username, (user) => {
PublicationActions.findByUser(user.userId);
});
UserStore.on('CHANGE', this._setUser);
// ...
},