I'm trying to have an observable that is based on a Subject, so that it gets updated every time the SUbject emits.
For some reason, when trying to fetch the value of the observable, first value is always null, and rest contain values. I shall expect to retrieve the latest emitted value of the observable. I've tried with all kind of SUbjects. What is what I'm missing?
...
activeBookings$: Subject<Booking[]> = new BehaviorSubject<Booking[]>(null);
activeBooking$: Observable<Booking> = this.activeBookings$.pipe(
filter((bookings) => bookings?.length > 0),
mergeMap((bookings) => this.getBookingById(bookings?.[0]?.id)),
share()
);
i = 0;
constructor(
public http: HttpClient,
public store: Store,
public asyncPipe: AsyncPipe,
public bookingStoreService: BookingStoreService
) {
this.getBookings().subscribe((bookings) => {
this.bookings$.next(bookings);
});
this.getActiveBookings().subscribe((bookings) => {
this.activeBookings$.next(bookings);
});
this.getPreviousBookings().subscribe((bookings) => {
this.previousBookings$.next(bookings);
});
}
...
Here is where I try to obtain the latest value:
...
const booking: Booking = this.asyncPipe.transform(
this.bookingService.activeBooking$
);
booking.status = status; //error: booking is null
...
You are using a BehaviorSubject here with an initial value of null:
activeBookings$: Subject<Booking[]> = new BehaviorSubject<Booking[]>(null);
You could initialise it with an empty array instead for exemple:
activeBookings$: Subject<Booking[]> = new BehaviorSubject<Booking[]>([]);
It would be more consistent type wise else your real type is:
activeBookings$: Subject<Booking[] | null> = new BehaviorSubject<Booking[] | null>(null);
Why are you getting null ? A BehaviorSubject needs to be initialised with a value and that is probably why the first value you get is null. A plain Subject doesn't need to contain an initial value and you would probably not get null
activeBookings$: Subject<Booking[]> = new Subject<Booking[]>()
A cleaner way to obtain the latest value without subscribing in your other service would be to leverage the features of the BehaviorSubject and the fact that calling activeBooking$.value makes you retrieve the latest value of the BehaviorSubject. Transform your activeBooking$ field so that it is a BehaviorSubject and when subscribing to the result of getActiveBookings(), you could do the computing that is currently in the pipe of the activeBooking$ definition there and call next on activeBooking$ with the result and expose a getter for your other service:
get activeBooking(): Booking {
return this.activeBooking$.value
}
Related
I subsribed to observable of behaviorSubject and it's triggers too many times. It happens only when i am navigating on the same component route, as an example...folder-folder-folder and now ...delete file triggers x3 times.
Subscribe code:
this.headerService.selectedItems.subscribe( {
next:(value) =>
if (this.selectedRowsIds.size >= 1 && value === true) {
this.deleteDocs();
}... and here value comes x3 times
BehaviorSubject:
deleteButton = new BehaviorSubject<any>({});
selectedItems = this.deleteButton.asObservable();
deleteTrigger(trigger: boolean) : void {
this.deleteButton.next(trigger);
}
I tried to unsubcribe, to send false trigger everytime when i navigate, but nothing changes.
I mention that component DOES NOT DESTROY in this case, cause we open folder-folder-folder on the same component, with changing route params.
The Problem can due to many other factors
Due to any change in route in the process.
Due to Not emitting value at right time.
Behavior Subject fires the value as soon as its initialized new BehaviorSubject<any>({}) with an {} (and empty object it emits and this value it holds )
Insufficient working and dependable code to see the flow , if possible please provide insight.
My Solution:
deleteButton :BehaviorSubject<boolean>= new BehaviorSubject<boolean>(false);
deleteTrigger(trigger: boolean) : void {
this.deleteButton.next(trigger);
}
trigger: boolean (here we are only emitting boolean values because input is always boolean)
this.headerService.deleteButton.subscribe(response => {
if(response) {
this.selectedRowsIds.size >= 1 && value === true ?
this.deleteDocs() : '';
}
});
this would subscribe to the action when a value is emitted from deleteButton and the value is true then it would excute desired logic though tenary opeartor
You can use rxjs takeLast with behavior subject to fix the issue or Promise Resolve. takeLast, you could also use take, takeOnce all found in rxjs. Also you do not need to strong type your interfaces on behavior subjects. It's recommended for security concerns. because any is applied.
Also you should make your subscription async and await the answer from the subscription.
RXJS LINK
rxjs
import { BehaviorSubject, takeLast, tap } from 'rxjs';
private yourSubject = new BehaviorSubject<any>({});
//or your choice
private yourSubject = new BehaviorSubject({});
//or your choice
private yourSubject = new BehaviorSubject({} as any);
public yourSubject$ = this.yourSubject.asObservable();
this.yourSubject$.pipe(takeLast(1),tap((item)=>{return item})).subscribe()
Promise resolve
this.yourSubject$.subscribe((element)=>{
const item = Promise.resolve(element)
return item;
});
//or your choice
this.yourSubject$.subscribe(async (element)=> {
const item = await element;
return item;
})
I am deleting a FRTDB node, I want to access deleted data from that node. the functions looks as follow:
exports.events = functions.database.ref('/events/{eventId}').onWrite(async (change, context) => {
const eventId = context.params.eventId
if (!change.after.exists() && change.before.exists()) {
//data removed
return Promise.all([admin.database().ref(`/events/${eventId}/dayofweek`).once('value')]).then(n => {
const pms = []
const days = n[0]
days.forEach(x => {
pms.push(admin.database().ref(`${change.before.val().active ? 'active' : 'inactive'}/${x.key}/${eventId}`).set(null))
})
return Promise.all(pms)
});
else {
return null;
}
})
The probem I am having is that
admin.database().ref(`/events/${eventId}/dayofweek
do not loop the data because it seems data is no longer there so the forEach is not working. How can I get access to this data and get to loop the deleted data?
Of course you won't be able to read data that was just deleted. The function runs after the delete is complete. If you want to get the data that was just deleted, you're supposed to use change.before as described in the documentation:
The Change object has a before property that lets you inspect what was
saved to Realtime Database before the event. The before property
returns a DataSnapshot where all methods (for example, val() and
exists()) refer to the previous value. You can read the new value
again by either using the original DataSnapshot or reading the after
property. This property on any Change is another DataSnapshot
representing the state of the data after the event happened.
The data that was deleted from the database is actually included in the call to your Cloud Function. You can get if from change.before.
exports.events = functions.database.ref('/events/{eventId}').onWrite(async (change, context) => {
const eventId = context.params.eventId
if (!change.after.exists() && change.before.exists()) {
//data removed
days = change.before.val().dayofweek;
...
})
So I am trying to get the value (the email) of my Observable<firebase.User>. I know there is something called BehaviourSubject, but I cannot use it since firebase.User requires an Observable, it seems.
retrieveUserData(){
let emailVal = "";
this.userData.subscribe(
{
next: x => emailVal += x.email && console.log(x.email),
error: err => console.error('Observer got an error: ' + err),
complete: () => console.log('Observer got a complete notification'),
}
);
return emailVal;
}
So my goal is to get x.email in the emailVal let, in order to pass it & display, for example.
The problem is, that I am getting an (by logging the whole method retrieveUserData()), but the console.log(x.email) always returns the value I am looking for.
Why is that & is there a way to get the value & store it in a string, let or something else?
It is because Observables are async. It means when you run subscribe method of the observable, it runs the command without blocking the current runtime. Also, you are assigning the value of emailVal when the observable is run but you return the value without waiting for the assignment to be happened.
What you can do?
You can keep a global variable to keep the email globally and use that variable to display the email in the html side.
#Component({
selector: "my-app",
// See here, I used emailVal to display it
template: "<span>{{emailVal}}</span>",
styles: [""]
})
export class TestComponent {
emailVal = "";
ngOnInit(): void {
this.retrieveUserData();
}
retrieveUserData(): void {
this.userData.subscribe(
x => this.handleData(x.email),
err => console.error("Observer got an error: " + err)
);
}
handleData(email) {
// Here, we assign the value of global variable (defined in class level)
this.emailVal = email;
console.log(email);
}
}
You can use rxjs library in such a way to make the observable return the value and return the observable in the method as below
retrieveUserData(): Observable<firebase.User> {
return this.userData.pipe(
map(x => x.email)
);
}
And in html side, using async pipe (as an example):
<span>{{retrieveUserData() | async}}</span>
async pipe will subscribe to observable and wait for it to complete and then take the value and put it in the value of span. You can check this StackBlitz example to understand this method deeply.
I want to create a subject without initial value.
I have created a ReplaySubject that buffers only the last value that been emitted.
The problem is that on the subscription is giving a null as initial value before it's being emitted.
My service:
private _replaySubjects = new ReplaySubject<Project>(1);
public replaySubjectAsObservable$ = this._replaySubjects.asObservable();
My component:
this.subjectSub = someService._projectList$.subscribe(value => {
console.log(value)
});
Rxjs version - 6.5.2
This won't emit any value until a significant value is emitted with this._replaySubject.next(newProject).
It has the added benefit that you can access the last emitted value outside of a subscription with this._replaySubject.getValue().
private _replaySubjects = new BehaviorSubject<Project>(null);
public replaySubjectAsObservable$ = this._replaySubjects.asObservable().pipe(skipWhile(project => project === null || project === undefined));;
I'm trying to work out how to use scan to derive a new state whenever my input observable emits a new value, but I can't seem to get it working.
I want to output a new State every time the input$ observable emits a new value, but it should be derived from the current value of state$.
Can anyone suggest how I can fix this? I have a feeling I've got the wrong idea altogether :-)
My code looks something like this:
const stateReducer = (state$: Observable<State>, input$: Observable<Input>) => {
state$ = state$.pipe( startWith(DEFAULT_STATE) );
const foo$: Observable<State> = input$.pipe(
filter((input) => isFoo(input)),
withLatestFrom(state$),
scan((acc, ([input, state]) => {
//returns derived state
});
const bar$: Observable<State> = input$.pipe(
filter((input) => isBar(input)),
withLatestFrom(state$),
scan((acc, ([input, state]) => {
//returns derived state
});
return merge(
foo$,
bar$
);
}
Since you want to use the result of an Observable as your seed value, switchMap will help.
switchMap docs
const bar$: Observable<State> = state$.pipe(
switchMap((state) => {
return input$.pipe(
filter((input) => isBar(input)),
scan((curState, input) => {
// do some logic here
return {...curState, prop: 'new value'}
}, state);
)
})
I've made a sample CodePen for a more complete solution at https://codepen.io/askmattcairns/pen/LYjEoZz?editors=0010.
More Details
switchMap means to switch to the stream in here. So this code is saying, once state$ emits a value, store it (as state), then wait for input$ to emit.
When we call scan, its seed value (the second property of scan) is now the result of state$'s emitted value.
This will emit a new value any time input$ receives a new value.
Update to Code Sandbox
After digging in to your Code Sandbox, I better understand what the problem is. You can see my final output at https://codesandbox.io/s/elegant-chaum-2b794?file=/src/index.tsx.
When you initialize your 2 inner streams foo$ and bar$, they both reference state$ using withLatestFrom. Each time input$ emits, it still references the original value of state, using 0 as its starting total.