Using React-Redux
I have a select list that when the user chooses one of the options, a item is created and placed in the database (if it matters, the reason its a select box is that there are multiple variations of the same item and what variation is important).
This is working correctly.
My problem is that I am not sure how I can get the id of the new item out of the redux store.
And just for chuckles, the prior developer set all this up with sagas. So I am still coming up to speed on how it all works together.
So when the select box is checked, the function checkFunction is called that calls the function createItem in the saga file. These functions are below:
in Repositories.jsx
checkFunction = (data) => {
const {createItem} = this.props;
// data holds the info that we need to send to the action
const created = createItem(data);
// once data comes back, redirect the user ot the details of the created item
// need id of created item
// navigateTo(`/item-details/${created.id}`);
}
in Repositories.saga.js
export function* createItem(action) {
try {
const {payload: newItem} = action;
// call api to create item
const created = yield call(requestAPI.post, ITEMS_URL, newItem);
// send message that its been done
yield put(actions.repositories.createItem.ok(created));
// send message to refresh item list
yield put(actions.inventories.fetchItems.start());
} catch (e) {
yield put(actions.repositories.createItem.fail(e));
}
}
I don't understand how to return the id of the created item once its created. I feel like I am missing something basic here. Can anyone point me in the right direction?
Actually getting data from saga back to react component is not trivial. There are multiple approaches to do what you need although each has its downside.
1. Call navigateTo in the saga.
export function* createItem(action) {
...
const created = yield call(requestAPI.post, ITEMS_URL, newItem);
yield call(navigateTo, `/item-details/${created.id}`)
}
This would be my recommended solution if you can get the navigateTo function into the saga. Navigation is a side effect and sagas are there to deal with side effects. Make sure to use the call effect, changing the url by directly calling the function can lead to some issues.
2. Store the latest created item id in redux store
In your reducer, when action actions.repositories.createItem.ok(created) is dispatched, store the created item info and then send the latest created item to the component. Finally you can use componentDidUpdate or useEffect to call navigateTo when the prop changes.
render() {
const created = Redux.useSelector(state => state.created);
useEffect(() => navigateTo(`/item-details/${created.id}`), [created])
...
}
This has the disadvantage that the component will rerender becuase of the changed created value.
3. Send callback in the createItem action
You can put a function into your action and then call it from the saga and essentially using it as a callback.
Component:
checkFunction = (data) => {
const {createItem} = this.props;
// data holds the info that we need to send to the action
const created = createItem(data, (created) => {
navigateTo(`/item-details/${created.id}`);
});
}
Saga:
export function* createItem(action) {
...
const created = yield call(requestAPI.post, ITEMS_URL, newItem);
action.callback(created)
...
}
The problem with this approach is that functions are not serializable and so you ideally should avoid them in your actions. Also, technically, there could be multiple sagas handling the same action and then it gets kind of confusing who should call the callback.
I have an Angular 2 service:
import {Storage} from './storage';
import {Injectable} from 'angular2/core';
import {Subject} from 'rxjs/Subject';
#Injectable()
export class SessionStorage extends Storage {
private _isLoggedInSource = new Subject<boolean>();
isLoggedIn = this._isLoggedInSource.asObservable();
constructor() {
super('session');
}
setIsLoggedIn(value: boolean) {
this.setItem('_isLoggedIn', value, () => {
this._isLoggedInSource.next(value);
});
}
}
Everything works great. But I have another component which doesn't need to subscribe, it just needs to get the current value of isLoggedIn at a certain point in time. How can I do this?
A Subject or Observable doesn't have a current value. When a value is emitted, it is passed to subscribers and the Observable is done with it.
If you want to have a current value, use BehaviorSubject which is designed for exactly that purpose. BehaviorSubject keeps the last emitted value and emits it immediately to new subscribers.
It also has a method getValue() to get the current value.
The only way you should be getting values "out of" an Observable/Subject is with subscribe!
If you're using getValue() you're doing something imperative in declarative paradigm. It's there as an escape hatch, but 99.9% of the time you should NOT use getValue(). There are a few interesting things that getValue() will do: It will throw an error if the subject has been unsubscribed, it will prevent you from getting a value if the subject is dead because it's errored, etc. But, again, it's there as an escape hatch for rare circumstances.
There are several ways of getting the latest value from a Subject or Observable in a "Rx-y" way:
Using BehaviorSubject: But actually subscribing to it. When you first subscribe to BehaviorSubject it will synchronously send the previous value it received or was initialized with.
Using a ReplaySubject(N): This will cache N values and replay them to new subscribers.
A.withLatestFrom(B): Use this operator to get the most recent value from observable B when observable A emits. Will give you both values in an array [a, b].
A.combineLatest(B): Use this operator to get the most recent values from A and B every time either A or B emits. Will give you both values in an array.
shareReplay(): Makes an Observable multicast through a ReplaySubject, but allows you to retry the observable on error. (Basically it gives you that promise-y caching behavior).
publishReplay(), publishBehavior(initialValue), multicast(subject: BehaviorSubject | ReplaySubject), etc: Other operators that leverage BehaviorSubject and ReplaySubject. Different flavors of the same thing, they basically multicast the source observable by funneling all notifications through a subject. You need to call connect() to subscribe to the source with the subject.
I had similar situation where late subscribers subscribe to the Subject after its value arrived.
I found ReplaySubject which is similar to BehaviorSubject works like a charm in this case.
And here is a link to better explanation: http://reactivex.io/rxjs/manual/overview.html#replaysubject
const observable = of('response')
function hasValue(value: any) {
return value !== null && value !== undefined;
}
function getValue<T>(observable: Observable<T>): Promise<T> {
return observable
.pipe(
filter(hasValue),
first()
)
.toPromise();
}
const result = await getValue(observable)
// Do the logic with the result
// .................
// .................
// .................
You can check the full article on how to implement it from here.
https://www.imkrish.com/blog/development/simple-way-get-value-from-observable
I encountered the same problem in child components where initially it would have to have the current value of the Subject, then subscribe to the Subject to listen to changes. I just maintain the current value in the Service so it is available for components to access, e.g. :
import {Storage} from './storage';
import {Injectable} from 'angular2/core';
import {Subject} from 'rxjs/Subject';
#Injectable()
export class SessionStorage extends Storage {
isLoggedIn: boolean;
private _isLoggedInSource = new Subject<boolean>();
isLoggedIn = this._isLoggedInSource.asObservable();
constructor() {
super('session');
this.currIsLoggedIn = false;
}
setIsLoggedIn(value: boolean) {
this.setItem('_isLoggedIn', value, () => {
this._isLoggedInSource.next(value);
});
this.isLoggedIn = value;
}
}
A component that needs the current value could just then access it from the service, i.e,:
sessionStorage.isLoggedIn
Not sure if this is the right practice :)
A similar looking answer was downvoted. But I think I can justify what I'm suggesting here for limited cases.
While it's true that an observable doesn't have a current value, very often it will have an immediately available value. For example with redux / flux / akita stores you may request data from a central store, based on a number of observables and that value will generally be immediately available.
If this is the case then when you subscribe, the value will come back immediately.
So let's say you had a call to a service, and on completion you want to get the latest value of something from your store, that potentially might not emit:
You might try to do this (and you should as much as possible keep things 'inside pipes'):
serviceCallResponse$.pipe(withLatestFrom(store$.select(x => x.customer)))
.subscribe(([ serviceCallResponse, customer] => {
// we have serviceCallResponse and customer
});
The problem with this is that it will block until the secondary observable emits a value, which potentially could be never.
I found myself recently needing to evaluate an observable only if a value was immediately available, and more importantly I needed to be able to detect if it wasn't. I ended up doing this:
serviceCallResponse$.pipe()
.subscribe(serviceCallResponse => {
// immediately try to subscribe to get the 'available' value
// note: immediately unsubscribe afterward to 'cancel' if needed
let customer = undefined;
// whatever the secondary observable is
const secondary$ = store$.select(x => x.customer);
// subscribe to it, and assign to closure scope
sub = secondary$.pipe(take(1)).subscribe(_customer => customer = _customer);
sub.unsubscribe();
// if there's a delay or customer isn't available the value won't have been set before we get here
if (customer === undefined)
{
// handle, or ignore as needed
return throwError('Customer was not immediately available');
}
});
Note that for all of the above I'm using subscribe to get the value (as #Ben discusses). Not using a .value property, even if I had a BehaviorSubject.
Although it may sound overkill, this is just another "possible" solution to keep Observable type and reduce boilerplate...
You could always create an extension getter to get the current value of an Observable.
To do this you would need to extend the Observable<T> interface in a global.d.ts typings declaration file. Then implement the extension getter in a observable.extension.ts file and finally include both typings and extension file to your application.
You can refer to this StackOverflow Answer to know how to include the extensions into your Angular application.
// global.d.ts
declare module 'rxjs' {
interface Observable<T> {
/**
* _Extension Method_ - Returns current value of an Observable.
* Value is retrieved using _first()_ operator to avoid the need to unsubscribe.
*/
value: Observable<T>;
}
}
// observable.extension.ts
Object.defineProperty(Observable.prototype, 'value', {
get <T>(this: Observable<T>): Observable<T> {
return this.pipe(
filter(value => value !== null && value !== undefined),
first());
},
});
// using the extension getter example
this.myObservable$.value
.subscribe(value => {
// whatever code you need...
});
There are two ways you can achieve this.
BehaviorSubject has a method getValue() which you can get the value in a specific point of time.
You can subscribe directly with the BehaviorSubject and you may pass the subscribed value to a class member, field or property.
I wouldn't recommend both approaches.
In the first approach, it's a convenient method you can get the value anytime, you may refer to this as the current snapshot at that point of time. Problem with this is you can introduce race conditions in your code, you may invoke this method in many different places and in different timing which is hard to debug.
The second approach is what most developers employ when they want a raw value upon subscription, you can track the subscription and when do you exactly unsubscribe to avoid further memory leak, you may use this if you're really desperate to bind it to a variable and there's no other ways to interface it.
I would recommend, looking again at your use cases, where do you use it? For example you want to determine if the user is logged in or not when you call any API, you can combine it other observables:
const data$ = apiRequestCall$().pipe(
// Latest snapshot from BehaviorSubject.
withLatestFrom(isLoggedIn),
// Allow call only if logged in.
filter(([request, loggedIn]) => loggedIn)
// Do something else..
);
With this, you may use it directly to the UI by piping data$ | async in case of angular.
A subscription can be created, then after taking the first emitted item, destroyed. In the example below, pipe() is a function that uses an Observable as its input and returns another Observable as its output, while not modifying the first observable.
Sample created with Angular 8.1.0 packages "rxjs": "6.5.3", "rxjs-observable": "0.0.7"
ngOnInit() {
...
// If loading with previously saved value
if (this.controlValue) {
// Take says once you have 1, then close the subscription
this.selectList.pipe(take(1)).subscribe(x => {
let opt = x.find(y => y.value === this.controlValue);
this.updateValue(opt);
});
}
}
You could store the last emitted value separately from the Observable. Then read it when needed.
let lastValue: number;
const subscription = new Service().start();
subscription
.subscribe((data) => {
lastValue = data;
}
);
The best way to do this is using Behaviur Subject, here is an example:
var sub = new rxjs.BehaviorSubject([0, 1])
sub.next([2, 3])
setTimeout(() => {sub.next([4, 5])}, 1500)
sub.subscribe(a => console.log(a)) //2, 3 (current value) -> wait 2 sec -> 4, 5
Another approach, If you want / can to use async await (has to be inside of an async functions) you can do this with modern Rxjs:
async myFunction () {
const currentValue = await firstValueFrom(
of(0).pipe(
withLatestFrom(this.yourObservable$),
map((tuple) => tuple[1]),
take(1)
)
);
// do stuff with current value
}
This will emit a value "Right away" because of withLatestFrom, and then will resolve the promise.
I'm doing a paginator with RxJS, I use a subject to centralize any time a new page is called. And on each event, I use exhaustMap to retrieve the page. That's prevent the getPage http call to be fired several time for the same page.
this._nextPage$.pipe(
exhaustMap(nextPageNumber => this.getPage(nextPageNumber))
).subscribe();
But I'd like to also show a spinner on each http pending.
With this code, how to reach the subscription of the merged http Observable ?
(in order to bind a pending component to the subscription)
Thx in advance !
[EDIT]
I need/prefer using a Subscription that hold the pending state itself for 2 main reasons:
I already use several custom components/directives based on Subscription
As it's used in many different places I hopped to manage the pending state without too much boilerplate code ...
Here is a simple example of component displaying pending action
#Component({
selector: 'anie-busy',
templateUrl: './busy.component.html',
styleUrls: ['./busy.component.scss']
})
export class BusyComponent implements OnChanges {
#Input() subscription;
isPending = false;
constructor() { }
ngOnChanges() {
if (this.subscription) {
this.isPending = true;
this.subscription.add(() => this.isPending = false);
} else {
this.isPending = false;
}
}
}
You can pipe do operator in between to accomplish that.
this._nextPage$.pipe(
tap(()=>{//loading},
exhaustMap(nextPageNumber => this.getPage(nextPageNumber),
tap(()=>{// stop loading})
).subscribe();
I have an XUL Overlay Firefox extension, I need to develop a dummy XUL extension that establishes connection with the original extension and sends a set of parameters (message) to the original extension. In short, I have to trigger my original extension with my dummy extension.
Probably the easiest way to do this is to have the original extension listening for a custom event on the base browser window. The dummy extension can then create and dispatch the event with whatever custom data is desired.
Creating and dispatching the event from the dummy:
function sendDataToMainExtension(data) {
if (typeof window === "undefined") {
//If there is no window defined, get the most recent.
var window=Components.classes["#mozilla.org/appshell/window-mediator;1"]
.getService(Components.interfaces.nsIWindowMediator)
.getMostRecentWindow("navigator:browser");
}
//This assumes that this event is being both sent from
// and received by privileged (main add-on) code.
var event = new CustomEvent('MyExtensionName-From-Dummy', { 'detail': data });
window.dispatchEvent(event);
}
You may need to take the same steps for making sure the data is visible on the receiving end as would be necessary when firing from privileged code to non-privileged code.
Listening for the event in main:
Components.utils.import("resource://gre/modules/Services.jsm");
const Ci = Components.interfaces;
//Listen for the event on all windows as it is unknown on which one
// the event will be sent.
function loadIntoWindow(myWindow) {
myWindow.addEventListener("MyExtensionName-From-Dummy",
receiveMessageFromDummy, false);
}
function unloadFromWindow(myWindow) {
myWindow.removeEventListener("MyExtensionName-From-Dummy",
receiveMessageFromDummy, false);
}
function forEachOpenWindow(fn) {
// Apply a function to all open browser windows
var windows = Services.wm.getEnumerator("navigator:browser");
let windowCount =0;
while (windows.hasMoreElements()) {
windowCount++;
fn(windows.getNext().QueryInterface(Ci.nsIDOMWindow));
}
}
function receiveMessageFromDummy(event) {
var dataFromDummy = event.detail;
//Do whatever was desired with the data.
}
var WindowListener = {
onOpenWindow: function(aWindow)
{
let domWindow = aWindow.QueryInterface(Ci.nsIInterfaceRequestor)
.getInterface(Ci.nsIDOMWindowInternal || Ci.nsIDOMWindow);
function onWindowLoad()
{
domWindow.removeEventListener("load",onWindowLoad);
if (domWindow.document.documentElement.getAttribute("windowtype")
== "navigator:browser") {
loadIntoWindow(domWindow);
}
}
domWindow.addEventListener("load",onWindowLoad);
},
onCloseWindow: function(xulWindow) { }, // Each window has an unload event handler.
onWindowTitleChange: function(xulWindow, newTitle) { }
};
//Listen for the custom event on all current browser windows.
forEachOpenWindow(loadIntoWindow);
//Listen for the custom event on any new browser window.
Services.wm.addListener(WindowListener);
The data sent should be available as event.detail within the receiveMessageFromDummy() function.
The code above provides one way communication. Two way communication is obtained just duplicating the code to communicate in the other direction with a different custom event. In other words, by having the main extension dispatching a different custom event called something like MyExtensionName-From-Main and having the dummy extension listening for that event. The code is exactly the same as above, but with the event name changed and the function called being receiveMessageFromMain().
Alternately, you could use Window.postMessage(). Doing so sends a "message" event for which you can listen. However, doing so leads to complications which are easier to avoid by using a custom event (e.g. you have to account for the fact that any code (i.e. some other random extension) could be using this event for their own purpose).
Note: The code to loop through windows was originally taken from Converting an old overlay-based Firefox extension into a restartless addon which that author re-wrote as the initial part of How to convert an overlay extension to restartless on MDN. It has been modified multiple times from that code. It may have even earlier versions from other sources.
I was wondering if anyone was aware of an example that shows multiple listeners to the YUI DDProxy, DD, or DDTarget onDragDrop event. Currently I have a hacked up example going. I'm using the http://developer.yahoo.com/yui/examples/datatable/dt_ddrows.html'>DataTable Reorder Rows example. I've modified it into several js classes. One object they override DDProxy for handles the onDragDrop event but it seems that if it's handled here then nothing else catches the event. Check the snip:
YAHOO.extend(YAHOO.example.DDRows, YAHOO.util.DDProxy, {
//snip
onDragDrop: function(e, id) {
if (id == "right-function-pane") {
alert("In the right drop place");
YAHOO.util.DragDropMgr.refreshCache();
}
}
In a class that does the creation of DDRows, I want to listen to the event here as well. Some actions are more appropriate in some places. I want to listen to the event in both places. Here's what I'm doing in class that builds the above:
onDropHandler: function(e, id) {
DragDropTable.myDataTable.deleteRow(YAHOO.util.DragDropMgr.dragCurrent.id);
},
rowInitializer: function() {
var i, id, myDDRow,
allRows = DragDropTable.myDataTable.getTbodyEl().rows;
for (i = 0; i < allRows.length; i++) {
id = allRows[i].id;
// Clean up any existing Drag instances
if (DragDropTable.myDTDrags[id]) {
DragDropTable.myDTDrags[id].unreg();
delete DragDropTable.myDTDrags[id];
}
// Create a Drag instance for each row
myDDRow = new YAHOO.example.DDRows(id);
myDDRow.srcData = DragDropTable.myDataTable.getRecord(id).getData();
myDDRow.onDragDrop = DragDropTable.onDropHandler;
DragDropTable.myDTDrags[id] = myDDRow;
}
}
It seems like if one is listening the other isn't working. I haven't found the syntax for allowing events to continue to bubble or to have multiple subscriptions to onDragDrop. Does anyone have the correct syntax?
Thanks
Josh Robinson