is there a way to update RXJS Subjects like Svelte stores with an instance of the current value?
Currently I have:
const materials = someMethodToGetResults();
const tmp = this.activeFilters.value;
tmp.materials = materials;
this.activeFilters.next(tmp);
which is not very elegant in my opinion. I always need to update the whole object where I only really need to update the properties. I think Svelte solves this super elegant, the stores have an update method like this:
this.activeFilters.update(current => {
current.materials = someMethodToGetResults();
return current;
});
Am I missing something? RXJS has a quite steep learning curve.
Thanks.
There's no built-in subject like this, but you can build your own
export class StoreSubject extends BehaviorSubject {
update(updateFn) {
this.next(updateFn(this.getValue());
}
}
Usage:
const store = new StoreSubject(initialValue);
// .. later:
store.update(val => {
// do something
return val
});
Related
I think I have read 100+ posts on the topic, and I still cannot figure out how to chain two HttpClient calls using rxjs in Angular 6.
Let's say I have a service with that signature:
GeoService {
getState(): Observable<string> {
return this.http.get<string>(stateURL);
}
getCities(state: string): Observable<string[]> {
return this.http.get<string[]>(citiesURL + state);
}
}
I can't for the life of me figure out how to obtain both the state and the corresponding list of cities in my component:
import { Observable } from 'rxjs';
import { map, flatMap, mergeMap, filter, switchMap } from 'rxjs/operators';
...
ngOnInit() {
this.svc.getState().
pipe(map((state) => {
this.state = state;
return this.svc.getCities(state);
}),
mergeMap((cities) => this.cities = cities))
).
subscribe(console.log('done'));
The code above in one of my 20 random attempts at combining pipe/map/mergeMap/subscribe in every way I could think of... a working example would be really really appreciated :)
Thanks!
Edit: None of the "possible duplicate" posts contain an actual example that works
The 21st attempt would have been correct ;-)
this.svc.getState().
pipe(mergeMap((state) => {
this.state = state;
return this.svc.getCities(state);
}),
tap((cities) => this.cities = cities)))
.subscribe(() => console.log('done'));
The chained Observable goes inside mergeMap. You can think of it as:
First, map the incoming notifaction to an Observable, then merge the resulting "inner" Observable into the "outer" Observable
Also, use tap instead of map if you intend to change an outside state.
You were almost there:
this.svc.getState().
pipe(
mergeMap((state) => {
return this.svc.getCities(state).pipe(map(cities => {
return { state: state, cities: cities }
}));
}),
).subscribe(stateAndCities => console.log(stateAndCities));
I advise you to read this article:
https://blog.strongbrew.io/rxjs-best-practices-in-angular/#using-pure-functions
It also explains why you shouldnt interact with global variables in rxjs operators.
You can do something like this
this.svc.getState().pipe(
tap(state=>this.state=state),
switchMap(this.svc.getCities))
.subscribe(cities=>{
//got the cities
})
the map operator is here to transform the emited value, but the tap operator is used to do something without modifying emited value of the observable.
note that switchMap(this.svc.getCities) is equivalent to switchMap(state=>this.svc.getCities(state)
I'm wanting to implement an Observable / Subject with 3 particular attributes
Remember last emitted value and be able to surface it via a getter (BehaviorSubject)
Only emit when value changes
It must have a strong type such that the getter is known to be available by a consumer (aka. BehaviorSubject.getValue())
I'm thinking of just extending BehaviorSubject but want to make sure I'm not introducing any potential gotchas based on my novice understanding.
export class DistinctUntilChangedBehaviorSubject<T, TValue> extends BehaviorSubject<T> {
constructor(
initialValue: T,
private _distinctKeySelector?: (value: T) => TValue,
private _comparer?: _Comparer<TValue, boolean>
) {
super(initialValue);
}
public subscribe() {
// I'm particularly interested in knowing if this has any gotchas.
// Mostly things like creating subscriptions that don't get disposed as expected.
return super.distinctUntilChanged(
this._distinctKeySelector,
this._comparer
).subscribe.apply(this, arguments);
}
}
So 2 questions:
Does this seem like a reasonable approach / are there any gotchas here?
Is there another preferred way of doing this?
I do not know really why, but I tend to prefer composition over extension.
So I would do something along these lines
import {BehaviorSubject} from 'rxjs';
export class BehaviourSubjectAugmented<T> {
bs: BehaviorSubject<T>;
constructor(initialValue: T, private comparer: (p: T, q: T) => boolean) {
this.bs = new BehaviorSubject(initialValue);
}
getValue() {
return this.bs.getValue();
}
asObservable() {
return this.bs.asObservable()
.distinctUntilChanged(this.comparer);
}
complete() {
return this.bs.complete();
}
next(value: T) {
return this.bs.next(value);
}
}
Turns out my original idea causes a call stack exceeded issue. I'm assuming that distinctUntilChanged must call subscribe internally thus causing infinite recursion.
I ended up finding a simpler way to get what I needed by simply adding a method to an ISubject instance.
function distinctUntilChangedBehaviorSubject(
initialValue: number
): ISubject<number> & { getValue(): number } {
const observer = new BehaviorSubject<number>(initialValue);
const observable = observer.distinctUntilChanged();
const subject: ISubject<number> = Subject.create(
observer,
observable
);
return Object.assign(
subject,
{
getValue: () => observer.getValue()
}
);
}
In the code following this description, I am trying to find and remove all these bad ListConfig objects that didn't have a group object set. It is correctly finding them, however it does not remove them. Is there something I am missing in the following code?
var Groups = [];
function queryForGroups(callback) {
var Group = Parse.Object.extend("Group");
var query = new Parse.Query(Group);
query.limit(1000);
query.find().then(function(result) {
Groups = result;
callback();
});
};
function removeConfigs(){
var Config = Parse.Object.extend("ListConfig");
var query = new Parse.Query(Config);
query.limit(10000);
query.notContainedIn("group", Groups);
query.find().then(function(configs){
return Parse.Object.destroyAll(configs, {useMasterKey:true});
});
}
function removeBadConfigs() {
queryForGroups(function() {
removeConfigs();
});
};
removeBadConfigs();
The code could be a little cleaner with respect to mixing promises, callbacks and an unnecessary global. Beyond that, it looks like it should work as long as your data model supports it. Specifically, your ListConfig object must have a "group" property, and it must have a Parse.Object value set for that property. The most common error I've seen is something like this:
var myGroup = // a parse object of type Group
myListConfig.set("group", myGroup.id); // WRONG
myListConfig.set("group", myGroup); // RIGHT
Assuming you've got that right, then it's mysterious why you're not seeing some deletes, but here's the code cleaned up with promises...
function queryForGroups() {
let query = new Parse.Query("Group")
query.limit(1000);
return query.find();
};
function removeConfigsWithGroups(groups){
let query = new Parse.Query("Config");
query.notContainedIn("group", groups);
return query.find().then(function(configs){
return Parse.Object.destroyAll(configs, {useMasterKey:true});
});
}
function removeBadConfigs() {
return queryForGroups(function(groups) {
return removeConfigsWithGroups(groups);
});
};
removeBadConfigs();
I figured it out. I removed "useMasterKey: true" because 1) it isn't needed for objects not with elevated privileges and 2) I was not running it in Cloud Code.
Is it anyway possible to submit also the empty values on a form? If not how do you properly initialize the form from state?
If it is not possible do I really need to do aField: this.props.data.aField || '' for every field I want to initialize? This seems like a lot of typing and repeating especially on forms which have FormSections and nesting.
If it would be possible I could just do something in the lines of this.
handleInit() {
const { patient, initialize } = this.props;
initialize({
patient.aField,
// Other fields
});
}
Not sure if this is applicable to your scenario, but you can specify initial form values in the mapStateToProps phase:
const mapStateToProps = state => {
return {
initialValues: { ...state.patient } // Use this property to set your initial data
};
}
This is also explained here: http://redux-form.com/6.6.1/examples/initializeFromState/
I am trying to extend the kendo scheduler using typescript.
Here is a working example using JS: http://jsbin.com/ucaB/1/edit
But I can't get it working in TypeScript.
I think I am close:
var CustomAgenda = (<any>kendo.ui).AgendaView.extend(
{
endDate: () =>
{
var self = this;
var d = (<any>kendo.ui).AgendaView.fn.endDate.call(self);
return (<any>kendo).date.addDays(d, 21);
}
});
But obviously my 'this' reference is wrong. The kendo typings file also doesn't expose kendo.date, or AgendaView, which makes things a little messy.
Maybe I am going about it wrong, this sure feels ugly...
In this particular case you dont want to capture the this outside the endDate function. So just use function and not ()=>:
var CustomAgenda = (<any>kendo.ui).AgendaView.extend(
{
endDate: function()
{
var self = this;
var d = (<any>kendo.ui).AgendaView.fn.endDate.call(self);
return (<any>kendo).date.addDays(d, 21);
}
});
This video might help clear things up a bit : https://www.youtube.com/watch?v=tvocUcbCupA&hd=1
Here is a more 'typescripty' method I came up with based on an answer to another question in the kendo forums. It uses datejs, but could easily be adapted to work without.
/// <reference path="../../typings/datejs/datejs.d.ts" />
declare module kendo.ui
{
class AgendaView implements kendo.ui.SchedulerView
{
startDate(): IDateJS;
endDate(): IDateJS;
}
}
class MonthlyAgendaView extends kendo.ui.AgendaView
{
endDate(): IDateJS
{
var date = this.startDate().clone();
date.moveToLastDayOfMonth();
return date;
}
}