Use one of the two incoming observable stream to filter data in angular - rxjs

I have the below code I have the formcontrol value (its a multicheckbox list), the values is an array of true/false, [true, false, true, true, ...]. This is one of the many lists
I also have the data list - collection of objects of structure {id: number, desc: string, disabled: boolean, selected: boolean}
I need to retrieve the id matching the true by matching index, and set an observable
I have the below code
valueChanged(e: any, name: string, key: string, valuesArray: string) {
this.hasChanged = true;
from(name).pipe(
debounceTime(200),
withLatestFrom(this[name].value), // this[name] is form array, gives [true, false, false,..]
mergeMap(values => forkJoin(
of(values),
this[valuesArray].find(val => val.name === name).data
)),
mergeMap(([values, data]) => {
flatMap((val, i) => val ? data[i].id : null),
filter(id => id !== null),
map(ids => {
this.selectedFilters[key] = ids;
switch (key) {
case 'groupId':
this.curGroup.next(ids);
break;
}
});
})
);
}
I need help on the line flatmap where I want to use values, for each of the value (true/false), if val[i] is true, include data[i].id in the output i want he id array of true values [1,2,4,5,..]. I get the error
Argument of type '([values, data]: [[string, unknown], unknown]) => void' is not assignable to parameter of type '(value: [[string, unknown], unknown], index: number) => ObservableInput'.
Type 'void' is not assignable to type 'ObservableInput'.ts(2345)
Need help on how to use solve this issue. Thanks in advance.

Related

I want to remove duplicate objects by name property by I am getting No overload matches this call in rxjs

allEmployees$ = this.http.get<IEmployees[]>('../../assets/employees').pipe(
map(allEmployees =>
allEmployees.map(Employee =>
<IEmployees>({
id: Employee.id,
name: Employee.name,
email: Employee.email,
gender: Employee.gender,
productid: Employee.productid,
productName: 'N/A',
})
)),
switchMap(data => data.reduce((acc, curr) => {
const exists = acc.find(v => v['name'] === curr['name']);
return exists ? acc : acc.concat(curr);
}, []))
);
Please see pic for more info:
It looks like the Typescript compiler is having trouble inferring the type of the array in the seed parameter of reduce. Try casting it to IEmployees[]

RxJS logic which solves a filter/merge issue

This is more a logical problem then a RxJS problem, I guess, but I do not get it how to solve it.
[input 1]
From a cities stream, I will receive 1 or 2 objects (cities1 or cities2 are test fixtures).
1 object if their is only one language available, 2 objects for a city with both languages.
[input 2]
I do also have a selectedLanguage ("fr" or "nl")
[algo]
If the language of the object corresponds the selectedLanguage, I will pluck the city. This works for my RxJS when I receive 2 objects (cities2)
But since I also can receive 1 object, the filter is not the right thing to do
[question]
Should I check the cities stream FIRST if only one object exists and add another object. Or what are better RxJS/logical options?
const cities1 = [
{city: "LEUVEN", language: "nl"}
];
const cities2 = [
{city: "BRUSSEL", language: "nl"},
{city: "BRUXELLES", language: "fr"}
];
const selectedLang = "fr"
const source$ = from(cities1);
const result = source$.pipe(
mergeMap((city) => {
return of(selectedLang).pipe(
map(lang => {
return {
lang: city.language,
city: city.city,
selectedLang: lang
}
}),
filter(a => a.lang === selectedLang),
pluck('city')
)
}
)
);
result.subscribe(console.log)
If selectedLang is not an observable (i.e. you don't want this to change) then I think it would make it way easier if you keep it as a value:
const result = source$.pipe(
filter(city => city.language === selectedLang)
map(city => city.city)
);
There's nothing wrong from using external parameters, and it makes the stream easier to read.
Now, if selectedLang is an observable, and you want result to always give the city with that selectedLang, then you probably need to combine both streams, while keeping all the cities received so far:
const selectedLang$ = of(selectedLang); // This is actually a stream that can change value
const cities$ = source$.pipe(
scan((acc, city) => [...acc, city], [])
);
const result = combineLatest([selectedLang$, cities$]).pipe(
map(([selectedLang, cities]) => cities.find(city => city.language == selectedLang)),
filter(found => Boolean(found))
map(city => city.city)
)
Edit: note that this result will emit every time cities$ or selectedLang$ changes and one of the cities matches. If you don't want repeats, you can use the distinctUntilChanged() operator - Probably this could be optimised using an exhaustMap or something, but it makes it harder to read IMO.
Thanks for your repsonse. It's great value for me. Indeed I will forget about the selectedLang$ and pass it like a regular string. Problem 1 solved
I'll explain a bit more in detail my question. My observable$ cities$ in fact is a GET and will always return 1 or 2 two rows.
leuven:
[ { city: 'LEUVEN', language: 'nl', selectedLanguage: 'fr' } ]
brussel:
[
{ city: 'BRUSSEL', language: 'nl', selectedLanguage: 'fr' },
{ city: 'BRUXELLES', language: 'fr', selectedLanguage: 'fr' }
]
In case it returns two rows I will be able to filter out the right value
filter(city => city.language === selectedLang) => BRUXELLES when selectedLangue is "fr"
But in case I only receive one row, I should always return this city.
What is the best solution to this without using if statements? I've been trying to work with object destruct and scaning the array but the result is always one record.
// HTTP get
const leuven: City[] = [ {city: "LEUVEN", language: "nl"} ];
// same HTTP get
const brussel: City[] = [ {city: "BRUSSEL", language: "nl"},
{city: "BRUXELLES", language: "fr"}
];
mapp(of(brussel), "fr").subscribe(console.log);
function mapp(cities$: Observable<City[]>, selectedLanguage: string): Observable<any> {
return cities$.pipe(
map(cities => {
return cities.map(city => { return {...city, "selectedLanguage": selectedLanguage }}
)
}),
// scan((acc, value) => [...acc, { ...value, selectedLanguage} ])
)
}

Filter an Array in an Observable

Here is an edited sample from learnrxjs. I want to filter the values in the type array. But thats not how it works: 'This condition will always return 'true' since the types 'string[]' and 'string' have no overlap.'
I am new to rxjs and cant figure out how to filter the array. Any advices? Is it possible?
const source = from([
{ name: 'Joe', age: 31, type: ['a', 'b'] },
{ name: 'Bob', age: 25, type: ['a'] }
]);
//filter out people with type b
const example = source.pipe(filter(person => person.type != 'a'));
//output: "People with type b: Bob"
const subscribe = example.subscribe(val => console.log(`Type a: ${val.name}`));
the filter() you are applying takes a function with signature T => boolean meaning that you will have to return a boolean true/false so it can filter out elements from the stream.
Your elements T are of type Object {name:string, age:number, type:array} so to filter on values in the type Array you will need to use the Array.indexOf prototype function:
source.pipe(filter(person => person.type.indexOf('b') == -1) // filter out people who have type b

Custom column filter on a formatted column

I need help using a custom column filter for handling JS objects.
I have a slickgrid table where the values in one column are JS object:
[
{ id: "1234", text: "Batman"},
{ id: "2345", text: "Robin"}
]
I use a custom formatter to smash the object into a string:
// convert [{id:string, text:string}...] to string
const optionFormatter: Formatter = (row, cell, value, columnDef, dataContext: any) =>
value ? value.map(o => o.text).join(', ') : '';
Which displays in slickgrid as
Batman, Robin
my slickgrid options use gridmenu and enables column filtering:
this.gridOptions = {
enableGridMenu: true,
enableFiltering: true,
enableAutoResize: true,
enableColumnReorder: true
};
My columnDef enables filtering for this column:
{
id: 'owners',
name: 'Owners',
field: 'owners',
sortable: false,
formatter: optionFormatter,
filterable: true
}
Everything works if the value in the cell is a string, but the filter doesn't work if the cell is an object. I assume the filter is searching the pre-formatted value.
Is there a way to provide the column with a custom filter function that knows how to search the JS object for the query string? For example if I could just search the JSON.stringify(value), that would be good enough.
Alternatively, this answer describes how I could use the formatter to store the formatted text as a different string property in dataContext. If I do that, how do I specify which property to filter, seeing as it is a different property than the column field?
I found a workaround.
preprocess my data, calling JSON.stringify on all values that are objects:
flattenFeature(f: Feature): any{
var res = {};
for (var prop in f) {
res[prop] = (typeof f[prop] === 'object') ? JSON.stringify(f[prop]) : f[prop];
}
return res;
}
Then in my formatter, I parse the json, before formatting:
// convert [{id:string, text:string}...] to string
const optionFormatter: Formatter = (row, cell, value, columnDef, dataContext) =>
value ? JSON.parse(value).map(o => o.text).join(', ') : '';
This allows the standard string filter to search the stringify'd JSON

Complex secondary index operations

I can create a secondary index for this table:
{
contact: [
'example#example.com'
]
}
Like this: indexCreate('contact', {multi: true})
But can I create index for this:
{
contact: [
{
type: 'email',
main: true
value: 'andros705#gmail.com'
}
{
type: 'phone'
value: '0735521632'
}
]
}
Secondary index would only search in objects whose type is 'email' and main is set to 'true'
Here's how you might create such an index:
table.indexCreate(
'email',
row => row('contact').filter({type: 'email'})('value'),
{multi: true})
This works by using a multi-index. When the multi: true argument is passed to indexCreate, the index function is expected to return an array instead of a single value. Every element in that array can be used to look up the document in the index (using getAll or between). If the array is empty, the document will not show up in the index.

Resources